From 7db048d9e8087f7cce77eadc3cbd3785baaf0427 Mon Sep 17 00:00:00 2001 From: Randolf Scholz Date: Wed, 13 Aug 2025 13:10:39 +0200 Subject: [PATCH 1/3] more cleanup and documentation cleanup fix param spec tuple interaction fix self check more cleanup use Unpack for 3.9 / 3.10 tests fixed crash in overload with multiple rvalues fixed basic tuple expression round trip fix bad paramspec interaction with typevartuple reworked star argument checking --- mypy/argmap.py | 219 ++++-- mypy/checker.py | 21 + mypy/checkexpr.py | 577 +++++++++----- mypy/constraints.py | 589 +++++++++----- mypy/expandtype.py | 2 +- mypy/infer.py | 346 +++++++- mypy/messages.py | 6 +- mypy/subtypes.py | 82 +- mypy/test/testconstraints.py | 117 ++- mypy/tuple_normal_form.py | 683 ++++++++++++++++ mypy/typeops.py | 8 +- mypy/types.py | 116 ++- mypyc/test-data/irbuild-lists.test | 18 +- mypyc/test-data/irbuild-tuple.test | 18 +- test-data/unit/check-abstract.test | 4 +- test-data/unit/check-basic.test | 4 +- test-data/unit/check-callable.test | 20 +- test-data/unit/check-class-namedtuple.test | 4 +- test-data/unit/check-classes.test | 40 +- test-data/unit/check-columns.test | 2 +- test-data/unit/check-dataclass-transform.test | 26 +- test-data/unit/check-dataclasses.test | 35 +- test-data/unit/check-deprecated.test | 6 +- test-data/unit/check-dynamic-typing.test | 14 +- test-data/unit/check-errorcodes.test | 2 +- test-data/unit/check-expressions.test | 30 +- test-data/unit/check-fastparse.test | 2 +- test-data/unit/check-flags.test | 2 +- test-data/unit/check-functions.test | 37 +- test-data/unit/check-functools.test | 20 +- test-data/unit/check-generics.test | 2 +- test-data/unit/check-ignore.test | 10 +- test-data/unit/check-incomplete-fixture.test | 36 +- test-data/unit/check-incremental.test | 5 +- test-data/unit/check-inference-context.test | 2 +- test-data/unit/check-inference.test | 2 +- test-data/unit/check-isinstance.test | 6 +- test-data/unit/check-kwargs.test | 16 +- test-data/unit/check-literal.test | 4 +- test-data/unit/check-modules.test | 14 +- test-data/unit/check-namedtuple.test | 4 +- test-data/unit/check-newsemanal.test | 6 +- test-data/unit/check-newtype.test | 2 +- test-data/unit/check-overloading.test | 69 +- .../unit/check-parameter-specification.test | 40 +- test-data/unit/check-plugin-attrs.test | 19 +- test-data/unit/check-protocols.test | 4 +- test-data/unit/check-python38.test | 3 +- test-data/unit/check-selftype.test | 2 +- test-data/unit/check-serialize.test | 44 +- test-data/unit/check-statements.test | 6 +- test-data/unit/check-super.test | 4 +- test-data/unit/check-tuples.test | 169 +++- test-data/unit/check-type-aliases.test | 7 +- test-data/unit/check-typeddict.test | 3 +- test-data/unit/check-typeform.test | 1 + test-data/unit/check-typevar-tuple.test | 62 +- test-data/unit/check-varargs.test | 736 +++++++++++++++++- test-data/unit/fine-grained-blockers.test | 16 +- .../unit/fine-grained-follow-imports.test | 4 +- test-data/unit/fine-grained-modules.test | 44 +- test-data/unit/fine-grained.test | 14 +- test-data/unit/fixtures/__init_subclass__.pyi | 9 +- test-data/unit/fixtures/__new__.pyi | 7 + test-data/unit/fixtures/alias.pyi | 9 +- test-data/unit/fixtures/any.pyi | 7 + test-data/unit/fixtures/async_await.pyi | 8 +- test-data/unit/fixtures/bool.pyi | 8 +- test-data/unit/fixtures/callable.pyi | 9 +- test-data/unit/fixtures/classmethod.pyi | 9 +- test-data/unit/fixtures/complex.pyi | 7 + test-data/unit/fixtures/complex_tuple.pyi | 11 +- test-data/unit/fixtures/dataclasses.pyi | 8 +- test-data/unit/fixtures/dict-full.pyi | 8 +- test-data/unit/fixtures/dict.pyi | 8 +- test-data/unit/fixtures/divmod.pyi | 8 +- test-data/unit/fixtures/enum.pyi | 8 +- test-data/unit/fixtures/exception.pyi | 6 +- test-data/unit/fixtures/f_string.pyi | 9 +- test-data/unit/fixtures/fine_grained.pyi | 8 +- test-data/unit/fixtures/float.pyi | 11 +- test-data/unit/fixtures/floatdict.pyi | 9 +- test-data/unit/fixtures/for.pyi | 9 +- test-data/unit/fixtures/function.pyi | 7 + test-data/unit/fixtures/isinstance.pyi | 9 +- .../unit/fixtures/isinstance_python3_10.pyi | 9 +- test-data/unit/fixtures/isinstancelist.pyi | 4 +- test-data/unit/fixtures/len.pyi | 3 +- test-data/unit/fixtures/list.pyi | 8 +- test-data/unit/fixtures/literal__new__.pyi | 7 + test-data/unit/fixtures/module.pyi | 8 +- test-data/unit/fixtures/module_all.pyi | 8 +- test-data/unit/fixtures/notimplemented.pyi | 8 +- test-data/unit/fixtures/object_hashable.pyi | 8 +- .../fixtures/object_with_init_subclass.pyi | 8 +- test-data/unit/fixtures/primitives.pyi | 4 +- test-data/unit/fixtures/property.pyi | 7 +- test-data/unit/fixtures/set.pyi | 8 +- test-data/unit/fixtures/slice.pyi | 8 +- test-data/unit/fixtures/type.pyi | 8 +- test-data/unit/fixtures/union.pyi | 9 +- test-data/unit/lib-stub/builtins.pyi | 4 + test-data/unit/lib-stub/dataclasses.pyi | 6 + test-data/unit/outputjson.test | 4 +- test-data/unit/pep561.test | 2 +- test-data/unit/typexport-basic.test | 6 + 106 files changed, 3831 insertions(+), 904 deletions(-) create mode 100644 mypy/tuple_normal_form.py diff --git a/mypy/argmap.py b/mypy/argmap.py index c30f6bd43f13..52ec7e569806 100644 --- a/mypy/argmap.py +++ b/mypy/argmap.py @@ -7,6 +7,8 @@ from mypy import nodes from mypy.maptype import map_instance_to_supertype +from mypy.nodes import ARG_NAMED, ARG_NAMED_OPT, ARG_OPT, ARG_POS, ARG_STAR, ARG_STAR2 +from mypy.tuple_normal_form import TupleHelper, TupleNormalForm from mypy.types import ( AnyType, Instance, @@ -16,6 +18,7 @@ TypedDictType, TypeOfAny, TypeVarTupleType, + UninhabitedType, UnpackType, get_proper_type, ) @@ -44,44 +47,35 @@ def map_actuals_to_formals( ambiguous_actual_kwargs: list[int] = [] fi = 0 for ai, actual_kind in enumerate(actual_kinds): - if actual_kind == nodes.ARG_POS: + if actual_kind == ARG_POS: if fi < nformals: - if not formal_kinds[fi].is_star(): + if formal_kinds[fi] in (ARG_POS, ARG_OPT): formal_to_actual[fi].append(ai) fi += 1 - elif formal_kinds[fi] == nodes.ARG_STAR: + elif formal_kinds[fi] == ARG_STAR: formal_to_actual[fi].append(ai) - elif actual_kind == nodes.ARG_STAR: - # We need to know the actual type to map varargs. - actualt = get_proper_type(actual_arg_type(ai)) - if isinstance(actualt, TupleType): - # A tuple actual maps to a fixed number of formals. - for _ in range(len(actualt.items)): - if fi < nformals: - if formal_kinds[fi] != nodes.ARG_STAR2: - formal_to_actual[fi].append(ai) - else: - break - if formal_kinds[fi] != nodes.ARG_STAR: - fi += 1 - else: - # Assume that it is an iterable (if it isn't, there will be - # an error later). - while fi < nformals: - if formal_kinds[fi].is_named(star=True): - break - else: - formal_to_actual[fi].append(ai) - if formal_kinds[fi] == nodes.ARG_STAR: - break - fi += 1 + elif actual_kind == ARG_STAR: + # convert the actual argument type to a tuple-like type + star_arg_type = TupleNormalForm.from_star_argument(actual_arg_type(ai)) + + # for a variadic argument use a negative value, so it remains truthy when decremented + # otherwise, use the length of the prefix. + num_actual_items = -1 if star_arg_type.is_variadic else len(star_arg_type.prefix) + # note: empty tuple star-args will not get mapped to anything + while fi < nformals and num_actual_items: + if formal_kinds[fi] in (ARG_POS, ARG_OPT, ARG_STAR): + formal_to_actual[fi].append(ai) + num_actual_items -= 1 + if formal_kinds[fi] in (ARG_STAR, ARG_NAMED, ARG_NAMED_OPT, ARG_STAR2): + break + fi += 1 elif actual_kind.is_named(): assert actual_names is not None, "Internal error: named kinds without names given" name = actual_names[ai] if name in formal_names and formal_kinds[formal_names.index(name)] != nodes.ARG_STAR: formal_to_actual[formal_names.index(name)].append(ai) - elif nodes.ARG_STAR2 in formal_kinds: - formal_to_actual[formal_kinds.index(nodes.ARG_STAR2)].append(ai) + elif ARG_STAR2 in formal_kinds: + formal_to_actual[formal_kinds.index(ARG_STAR2)].append(ai) else: assert actual_kind == nodes.ARG_STAR2 actualt = get_proper_type(actual_arg_type(ai)) @@ -171,18 +165,86 @@ def __init__(self, context: ArgumentInferContext) -> None: # Type context for `*` and `**` arg kinds. self.context = context + def parse_star_argument(self, star_arg: Type, /) -> TupleType: + r"""Parse the type of ``*args`` argument into a tuple type. + + Note: For star parameters, use `parse_star_parameter` instead. + """ + tnf = TupleNormalForm.from_star_argument(star_arg) + return tnf.materialize(self.context) + + def parse_star_parameter(self, star_param: Type, /) -> TupleType: + r"""Parse the type of a ``*args: T`` parameter into a tuple type. + + This is different from `parse_star_argument` since mypy does some translation + for certain annotations. Below are some examples of how this works. + + | annotation | semanal result | parsed result | + |-----------------------|-----------------------|-------------------------| + | *args: int | int | tuple[*tuple[int, ...]] | + | *args: *tuple[T, ...] | Unpack[tuple[T, ...]] | tuple[*tuple[T, ...]] | + | *args: *tuple[A, B] | Unpack[tuple[A, B]] | tuple[A, B] | + | *args: *Ts | Unpack[Ts] | tuple[*Ts] | + | *args: P.args | P.args | tuple[*P.args] | + """ + p_t = get_proper_type(star_param) + if isinstance(p_t, UnpackType): + unpacked = get_proper_type(p_t.type) + if isinstance(unpacked, TupleType): + return unpacked + return TupleType([p_t], fallback=self.context.fallback_tuple) + + elif isinstance(p_t, ParamSpecType): + # We put the ParamSpec inside an UnpackType. + parsed = UnpackType(p_t) + return TupleType([parsed], fallback=self.context.fallback_tuple) + + else: # e.g. *args: int --> *args: *tuple[int, ...] + parsed = UnpackType(self.context.make_tuple_instance_type(p_t)) + return TupleType([parsed], fallback=self.context.fallback_tuple) + + @staticmethod + def unparse_star_parameter(t: Type, /) -> Type: + r"""Reverse normalizations done by parse_star_parameter. + + tuple[*tuple[T, ...]] -> T + tuple[A, B] -> *tuple[A, B] + tuple[*Ts] -> *Ts + tuple[*P.args] -> P.args + """ + p_t = get_proper_type(t) + assert isinstance(p_t, TupleType), f"Expected a parsed star argument, got {t}" + simplified_type = p_t.simplify() + + # convert tuple[T, ...] to plain T. + if isinstance(simplified_type, Instance): + assert simplified_type.type.fullname == "builtins.tuple" + return simplified_type.args[0] + # wrap tuple and Ts in UnpackType + elif isinstance(simplified_type, (TupleType, TypeVarTupleType)): + return UnpackType(simplified_type) + # return ParamSpec as is. + elif isinstance(simplified_type, ParamSpecType): + return simplified_type + else: + assert False, f"Unexpected unpack content {simplified_type!r}" + def expand_actual_type( self, actual_type: Type, actual_kind: nodes.ArgKind, formal_name: str | None, formal_kind: nodes.ArgKind, - allow_unpack: bool = False, ) -> Type: """Return the actual (caller) type(s) of a formal argument with the given kinds. - If the actual argument is a tuple *args, return the next individual tuple item that - maps to the formal arg. + If the actual argument is a star argument *args, then: + 1. If the formal argument is positional, return the next individual tuple item that + maps to the formal arg. + If the tuple is exhausted, returns UninhabitedType. + 2. If the formal argument is a star parameter, returns a tuple type with the items + that map to the formal arg by slicing. + If the tuple is exhausted, returns an empty tuple type. If the actual argument is a TypedDict **kwargs, return the next matching typed dict value type based on formal argument name and kind. @@ -192,51 +254,56 @@ def expand_actual_type( """ original_actual = actual_type actual_type = get_proper_type(actual_type) - if actual_kind == nodes.ARG_STAR: - if isinstance(actual_type, TypeVarTupleType): - # This code path is hit when *Ts is passed to a callable and various - # special-handling didn't catch this. The best thing we can do is to use - # the upper bound. - actual_type = get_proper_type(actual_type.upper_bound) - if isinstance(actual_type, Instance) and actual_type.args: - from mypy.subtypes import is_subtype - - if is_subtype(actual_type, self.context.iterable_type): - return map_instance_to_supertype( - actual_type, self.context.iterable_type.type - ).args[0] - else: - # We cannot properly unpack anything other - # than `Iterable` type with `*`. - # Just return `Any`, other parts of code would raise - # a different error for improper use. - return AnyType(TypeOfAny.from_error) - elif isinstance(actual_type, TupleType): - # Get the next tuple item of a tuple *arg. - if self.tuple_index >= len(actual_type.items): - # Exhausted a tuple -- continue to the next *args. - self.tuple_index = 1 - else: - self.tuple_index += 1 - item = actual_type.items[self.tuple_index - 1] - if isinstance(item, UnpackType) and not allow_unpack: - # An unpack item that doesn't have special handling, use upper bound as above. - unpacked = get_proper_type(item.type) - if isinstance(unpacked, TypeVarTupleType): - fallback = get_proper_type(unpacked.upper_bound) - else: - fallback = unpacked - assert ( - isinstance(fallback, Instance) - and fallback.type.fullname == "builtins.tuple" - ) - item = fallback.args[0] - return item - elif isinstance(actual_type, ParamSpecType): - # ParamSpec is valid in *args but it can't be unpacked. - return actual_type + + if actual_kind == ARG_STAR: + assert formal_kind in (ARG_POS, ARG_OPT, ARG_STAR) + # parse *args into a TupleType. + tuple_helper = TupleHelper(self.context.tuple_typeinfo) + star_args_type = self.parse_star_argument(actual_type) + + # # star_args_type failed to parse. treat as if it were tuple[Any, ...] + # if isinstance(star_args_type, AnyType): + # any_tuple = self.context.make_tuple_instance_type(AnyType(TypeOfAny.from_error)) + # star_args_type = self.context.make_tuple_type([UnpackType(any_tuple)]) + + assert isinstance(star_args_type, TupleType) + + # we are mapping an actual *args to positional arguments. + if formal_kind in (ARG_POS, ARG_OPT): + value = tuple_helper.get_item(star_args_type, self.tuple_index) + self.tuple_index += 1 + + # FIXME: In principle, None should indicate out-of-bounds access + # caused by an error in formal_to_actual mapping. + # assert value is not None, "error in formal_to_actual mapping" + # However, in some cases due to lack of machinery it can happen: + # For example f(*[]). Then formal_to_actual is ignorant of the fact + # that the list is empty, but when materializing the tuple we actually get an empty tuple. + # Therefore, we currently just return UninhabitedType in this case. + value = UninhabitedType() if value is None else value + + # if the argument is exhausted, reset the index + if ( + not star_args_type.is_variadic + and self.tuple_index >= star_args_type.minimum_length + ): + self.tuple_index = 0 + return value + + # we are mapping an actual *args input to a *args formal argument. + elif formal_kind == ARG_STAR: + # get the slice from the current index to the end of the tuple. + r = tuple_helper.get_slice(star_args_type, self.tuple_index, None) + # r = star_args_type.slice( + # self.tuple_index, None, None, fallback=self.context.tuple_type + # ) + self.tuple_index = 0 + # assert r is not None, f"failed to slice {star_args_type} at {self.tuple_index}" + return r + else: - return AnyType(TypeOfAny.from_error) + raise AssertionError(f"Unexpected formal kind {formal_kind} for *args") + elif actual_kind == nodes.ARG_STAR2: from mypy.subtypes import is_subtype diff --git a/mypy/checker.py b/mypy/checker.py index 96e41a5e1786..94855a080841 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -4259,6 +4259,27 @@ def check_multi_assignment_from_tuple( # inferred return type for an overloaded function # to be ambiguous. return + if ( + isinstance(reinferred_rvalue_type, Instance) + and reinferred_rvalue_type.type.fullname == "builtins.tuple" + ): + # the type can change into variadic tuple if the added context picks a different overload + # see testOverloadWithOverlappingItemsAndAnyArgument17 as an example + rv_type = reinferred_rvalue_type.args[0] + for lv in lvalues: + if ( + isinstance(lv, NameExpr) + and isinstance(lv.node, Var) + and lv.node.type is None + ): + self.check_assignment( + lv, self.temp_node(rv_type, context), infer_lvalue_type + ) + elif isinstance(lv, StarExpr): + list_expr = ListExpr([StarExpr(self.temp_node(rv_type, context))]) + list_expr.set_line(context) + self.check_assignment(lv.expr, list_expr, infer_lvalue_type) + return assert isinstance(reinferred_rvalue_type, TupleType) rvalue_type = reinferred_rvalue_type diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 9990caaeb7a1..f7c6a0611634 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -5,7 +5,7 @@ import enum import itertools import time -from collections import defaultdict +from collections import defaultdict, deque from collections.abc import Callable, Iterable, Iterator, Sequence from contextlib import contextmanager, nullcontext from typing import ClassVar, Final, TypeAlias as _TypeAlias, cast, overload @@ -19,6 +19,7 @@ from mypy.checkmember import analyze_member_access, has_operator from mypy.checkstrformat import StringFormatterChecker from mypy.constant_fold import constant_fold_expr +from mypy.constraints import ParsedActual from mypy.erasetype import erase_type, remove_instance_last_known_values, replace_meta_vars from mypy.errors import ErrorInfo, ErrorWatcher, report_internal_error from mypy.expandtype import ( @@ -36,6 +37,8 @@ from mypy.messages import MessageBuilder, format_type from mypy.nodes import ( ARG_NAMED, + ARG_NAMED_OPT, + ARG_OPT, ARG_POS, ARG_STAR, ARG_STAR2, @@ -132,6 +135,7 @@ has_await_expression, has_str_expression, ) +from mypy.tuple_normal_form import TupleHelper, TupleNormalForm from mypy.tvar_scope import TypeVarLikeScope from mypy.typeanal import ( TypeAnalyser, @@ -2269,7 +2273,10 @@ def infer_function_type_arguments_pass2( def argument_infer_context(self) -> ArgumentInferContext: if self._arg_infer_context_cache is None: self._arg_infer_context_cache = ArgumentInferContext( - self.chk.named_type("typing.Mapping"), self.chk.named_type("typing.Iterable") + self.chk.named_type("typing.Mapping"), + self.chk.named_type("typing.Iterable"), + self.chk.named_type("builtins.function"), + self.chk.named_type("builtins.tuple").type, ) return self._arg_infer_context_cache @@ -2375,49 +2382,57 @@ def check_argument_count( ) # Check for too many or few values for formals. - for i, kind in enumerate(callee.arg_kinds): - mapped_args = formal_to_actual[i] - if kind.is_required() and not mapped_args and not is_unexpected_arg_error: - # No actual for a mandatory formal - if kind.is_positional(): + for i, actuals in enumerate(formal_to_actual): + formal_kind = callee.arg_kinds[i] + if not actuals: + if callee.param_spec() is not None and callee.special_sig != "partial": + self.msg.too_few_arguments(callee, context, actual_names) + ok = False + elif formal_kind == ARG_POS and not is_unexpected_arg_error: + # No actuals for a mandatory formal self.msg.too_few_arguments(callee, context, actual_names) if object_type and callable_name and "." in callable_name: self.missing_classvar_callable_note(object_type, callable_name, context) - else: + ok = False + elif formal_kind == ARG_NAMED and not is_unexpected_arg_error: argname = callee.arg_names[i] or "?" self.msg.missing_named_argument(callee, context, argname) - ok = False - elif not kind.is_star() and is_duplicate_mapping( - mapped_args, actual_types, actual_kinds - ): - if self.chk.in_checked_function() or isinstance( - get_proper_type(actual_types[mapped_args[0]]), TupleType - ): - self.msg.duplicate_argument_value(callee, i, context) ok = False - elif ( - kind.is_named() - and mapped_args - and actual_kinds[mapped_args[0]] not in [nodes.ARG_NAMED, nodes.ARG_STAR2] - ): - # Positional argument when expecting a keyword argument. - self.msg.too_many_positional_arguments(callee, context) - ok = False - elif callee.param_spec() is not None: - if not mapped_args and callee.special_sig != "partial": - self.msg.too_few_arguments(callee, context, actual_names) - ok = False - elif len(mapped_args) > 1: + elif formal_kind == ARG_STAR: + # check if the star argument has a minimum size (e.g. *tuple[*tuple[int, ...], int]) + star_param_type = TupleNormalForm.from_star_parameter(callee.arg_types[i]) + if star_param_type.minimum_length: + self.msg.too_few_arguments(callee, context, actual_names) + else: + if callee.param_spec() is not None and len(actuals) > 1: paramspec_entries = sum( isinstance(get_proper_type(actual_types[k]), ParamSpecType) - for k in mapped_args + for k in actuals ) - if actual_kinds[mapped_args[0]] == nodes.ARG_STAR and paramspec_entries > 1: + if actual_kinds[actuals[0]] == ARG_STAR and paramspec_entries > 1: self.msg.fail("ParamSpec.args should only be passed once", context) ok = False - if actual_kinds[mapped_args[0]] == nodes.ARG_STAR2 and paramspec_entries > 1: + if actual_kinds[actuals[0]] == ARG_STAR2 and paramspec_entries > 1: self.msg.fail("ParamSpec.kwargs should only be passed once", context) ok = False + + elif ( + formal_kind in (ARG_POS, ARG_OPT, ARG_NAMED, ARG_NAMED_OPT) + and is_duplicate_mapping(actuals, actual_types, actual_kinds) + and ( + self.chk.in_checked_function() + or isinstance(get_proper_type(actual_types[actuals[0]]), TupleType) + ) + ): + self.msg.duplicate_argument_value(callee, i, context) + ok = False + + elif formal_kind in (ARG_NAMED, ARG_NAMED_OPT) and ( + actual_kinds[actuals[0]] not in [ARG_NAMED, ARG_STAR2] + ): + # Positional argument when expecting a keyword argument. + self.msg.too_many_positional_arguments(callee, context) + ok = False return ok def check_for_extra_actual_arguments( @@ -2439,45 +2454,54 @@ def check_for_extra_actual_arguments( ok = True # False if we've found any error for i, kind in enumerate(actual_kinds): - if ( - i not in all_actuals - and - # We accept the other iterables than tuple (including Any) - # as star arguments because they could be empty, resulting no arguments. - (kind != nodes.ARG_STAR or is_non_empty_tuple(actual_types[i])) - and - # Accept all types for double-starred arguments, because they could be empty - # dictionaries and we can't tell it from their types - kind != nodes.ARG_STAR2 - ): - # Extra actual: not matched by a formal argument. - ok = False - if kind != nodes.ARG_NAMED: - self.msg.too_many_arguments(callee, context) - else: + if i not in all_actuals: + if kind == ARG_POS: + ok = False + self.msg.too_many_positional_arguments(callee, context) + + elif kind == ARG_NAMED: + ok = False assert actual_names, "Internal error: named kinds without names given" act_name = actual_names[i] assert act_name is not None act_type = actual_types[i] self.msg.unexpected_keyword_argument(callee, act_name, act_type, context) is_unexpected_arg_error = True - elif ( - kind == nodes.ARG_STAR and nodes.ARG_STAR not in callee.arg_kinds - ) or kind == nodes.ARG_STAR2: - actual_type = get_proper_type(actual_types[i]) - if isinstance(actual_type, (TupleType, TypedDictType)): - if all_actuals.get(i, 0) < len(actual_type.items): - # Too many tuple/dict items as some did not match. - if kind != nodes.ARG_STAR2 or not isinstance(actual_type, TypedDictType): - self.msg.too_many_arguments(callee, context) - else: - self.msg.too_many_arguments_from_typed_dict( - callee, actual_type, context - ) - is_unexpected_arg_error = True + + elif kind == ARG_STAR: + star_arg_type = TupleNormalForm.from_star_argument(actual_types[i]) + if star_arg_type.minimum_length > 0: ok = False - # *args/**kwargs can be applied even if the function takes a fixed - # number of positional arguments. This may succeed at runtime. + self.msg.too_many_positional_arguments(callee, context) + + elif kind == ARG_STAR2: + kwargs_type = get_proper_type(actual_types[i]) + if isinstance(kwargs_type, TypedDictType) and kwargs_type.items: + ok = False + self.msg.too_many_arguments_from_typed_dict(callee, kwargs_type, context) + is_unexpected_arg_error = True + else: + assert False, f"Unexpected argument kind {kind}" + + else: # i in all_actuals + if kind == ARG_STAR: + star_arg_type = TupleNormalForm.from_star_argument(actual_types[i]) + if (ARG_STAR not in callee.arg_kinds) and ( + star_arg_type.minimum_length > all_actuals[i] + ): + # Too many tuple items as some did not match. + ok = False + self.msg.too_many_positional_arguments(callee, context) + + elif kind == ARG_STAR2: + kwargs_type = get_proper_type(actual_types[i]) + if isinstance(kwargs_type, TypedDictType) and ( + len(kwargs_type.items) > all_actuals[i] + ): + # Too many dict items as some did not match. + ok = False + self.msg.too_many_arguments_from_typed_dict(callee, kwargs_type, context) + is_unexpected_arg_error = True return ok, is_unexpected_arg_error @@ -2532,114 +2556,71 @@ def check_argument_types( mapper = ArgTypeExpander(self.argument_infer_context()) for i, actuals in enumerate(formal_to_actual): - orig_callee_arg_type = get_proper_type(callee.arg_types[i]) - - # Checking the case that we have more than one item but the first argument - # is an unpack, so this would be something like: - # [Tuple[Unpack[Ts]], int] - # - # In this case we have to check everything together, we do this by re-unifying - # the suffices to the tuple, e.g. a single actual like - # Tuple[Unpack[Ts], int] - expanded_tuple = False - actual_kinds = [arg_kinds[a] for a in actuals] - if len(actuals) > 1: - p_actual_type = get_proper_type(arg_types[actuals[0]]) - if ( - isinstance(p_actual_type, TupleType) - and len(p_actual_type.items) == 1 - and isinstance(p_actual_type.items[0], UnpackType) - and actual_kinds == [nodes.ARG_STAR] + [nodes.ARG_POS] * (len(actuals) - 1) - ): - actual_types = [p_actual_type.items[0]] + [arg_types[a] for a in actuals[1:]] - if isinstance(orig_callee_arg_type, UnpackType): - p_callee_type = get_proper_type(orig_callee_arg_type.type) - if isinstance(p_callee_type, TupleType): - assert p_callee_type.items - callee_arg_types = p_callee_type.items - callee_arg_kinds = [nodes.ARG_STAR] + [nodes.ARG_POS] * ( - len(p_callee_type.items) - 1 - ) - expanded_tuple = True - - if not expanded_tuple: - actual_types = [arg_types[a] for a in actuals] - if isinstance(orig_callee_arg_type, UnpackType): - unpacked_type = get_proper_type(orig_callee_arg_type.type) - if isinstance(unpacked_type, TupleType): - inner_unpack_index = find_unpack_in_list(unpacked_type.items) - if inner_unpack_index is None: - callee_arg_types = unpacked_type.items - callee_arg_kinds = [ARG_POS] * len(actuals) - else: - inner_unpack = unpacked_type.items[inner_unpack_index] - assert isinstance(inner_unpack, UnpackType) - inner_unpacked_type = get_proper_type(inner_unpack.type) - if isinstance(inner_unpacked_type, TypeVarTupleType): - # This branch mimics the expanded_tuple case above but for - # the case where caller passed a single * unpacked tuple argument. - callee_arg_types = unpacked_type.items - callee_arg_kinds = [ - ARG_POS if i != inner_unpack_index else ARG_STAR - for i in range(len(unpacked_type.items)) - ] - else: - # We assume heterogeneous tuples are desugared earlier. - assert isinstance(inner_unpacked_type, Instance) - assert inner_unpacked_type.type.fullname == "builtins.tuple" - callee_arg_types = ( - unpacked_type.items[:inner_unpack_index] - + [inner_unpacked_type.args[0]] - * (len(actuals) - len(unpacked_type.items) + 1) - + unpacked_type.items[inner_unpack_index + 1 :] - ) - callee_arg_kinds = [ARG_POS] * len(actuals) - elif isinstance(unpacked_type, TypeVarTupleType): - callee_arg_types = [orig_callee_arg_type] - callee_arg_kinds = [ARG_STAR] - else: - assert isinstance(unpacked_type, Instance) - assert unpacked_type.type.fullname == "builtins.tuple" - callee_arg_types = [unpacked_type.args[0]] * len(actuals) - callee_arg_kinds = [ARG_POS] * len(actuals) - else: - callee_arg_types = [orig_callee_arg_type] * len(actuals) - callee_arg_kinds = [callee.arg_kinds[i]] * len(actuals) + # missing actuals are checked in check_argument_count + if not actuals: + continue - assert len(actual_types) == len(actuals) == len(actual_kinds) + formal_type = callee.arg_types[i] + formal_kind = callee.arg_kinds[i] + formal_name = callee.arg_names[i] + + if formal_kind in (ARG_POS, ARG_OPT, ARG_NAMED, ARG_NAMED_OPT, ARG_STAR2): + # these cases are all easy, we just need to check the actuals one by one + # Note: for ARG_POS, ARG_OPT, ARG_NAMED, ARG_NAMED_OPT multiple actuals + # are a TOO_MANY_ARGUMENTS error, which is reported in check_argument_count. + for a in actuals: + expanded_actual = mapper.expand_actual_type( + arg_types[a], arg_kinds[a], formal_name, formal_kind + ) + check_arg( + expanded_actual, + arg_types[a], + arg_kinds[a], + formal_type, + a + 1, + i + 1, + callee, + object_type, + args[a], + context, + ) - if len(callee_arg_types) != len(actual_types): - if len(actual_types) > len(callee_arg_types): - self.chk.msg.too_many_arguments(callee, context) - else: - self.chk.msg.too_few_arguments(callee, context, None) - continue + elif formal_kind == ARG_STAR: + # parse the formal type into a TupleType. + formal_tuple = mapper.parse_star_parameter(formal_type) + # non-variadic star parameters are converted to plain ARG_POS during normalization + assert formal_tuple.unpack_index is not None, "Expected variadic star parameter" + + # Parse all the actuals in order + parsed_actuals = [ + ParsedActual( + id=actual, + kind=arg_kinds[actual], + type=arg_types[actual], + expanded=mapper.expand_actual_type( + arg_types[actual], arg_kinds[actual], None, formal_kind + ), + ) + for actual in actuals + ] - assert len(callee_arg_types) == len(actual_types) - assert len(callee_arg_types) == len(callee_arg_kinds) - for actual, actual_type, actual_kind, callee_arg_type, callee_arg_kind in zip( - actuals, actual_types, actual_kinds, callee_arg_types, callee_arg_kinds - ): - # Check that a *arg is valid as varargs. - expanded_actual = mapper.expand_actual_type( - actual_type, - actual_kind, - callee.arg_names[i], - callee_arg_kind, - allow_unpack=isinstance(callee_arg_type, UnpackType), - ) - check_arg( - expanded_actual, - actual_type, - actual_kind, - callee_arg_type, - actual + 1, - i + 1, - callee, - object_type, - args[actual], - context, - ) + # for each actual, determine the expected type from the formal tuple + expected_types = map_actuals_to_star_parameter(formal_tuple, parsed_actuals) + assert len(expected_types) == len(parsed_actuals) + + for parsed_actual, expected_type in zip(parsed_actuals, expected_types): + check_arg( + parsed_actual.expanded, + parsed_actual.type, + parsed_actual.kind, + expected_type, + parsed_actual.id + 1, + i + 1, + callee, + object_type, + args[parsed_actual.id], + context, + ) def check_arg( self, @@ -2647,8 +2628,8 @@ def check_arg( original_caller_type: Type, caller_kind: ArgKind, callee_type: Type, - n: int, - m: int, + actual_arg_index: int, # 1-based + formal_arg_index: int, # 1-based callee: CallableType, object_type: Type | None, context: Context, @@ -2666,8 +2647,8 @@ def check_arg( self.msg.concrete_only_call(callee_type, context) elif not is_subtype(caller_type, callee_type, options=self.chk.options): error = self.msg.incompatible_argument( - n, - m, + actual_arg_index, + formal_arg_index, callee, original_caller_type, caller_kind, @@ -5213,9 +5194,6 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type: if type_context_items is not None: unpack_in_context = find_unpack_in_list(type_context_items) is not None seen_unpack_in_items = False - allow_precise_tuples = ( - unpack_in_context or PRECISE_TUPLE_TYPES in self.chk.options.enable_incomplete_feature - ) # Infer item types. Give up if there's a star expression # that's not a Tuple. @@ -5238,35 +5216,19 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type: else: ctx = None tt = self.accept(item.expr, ctx) - tt = get_proper_type(tt) - if isinstance(tt, TupleType): - if find_unpack_in_list(tt.items) is not None: - if seen_unpack_in_items: - # Multiple unpack items are not allowed in tuples, - # fall back to instance type. - return self.check_lst_expr(e, "builtins.tuple", "") - else: - seen_unpack_in_items = True - items.extend(tt.items) - # Note: this logic depends on full structure match in tuple_context_matches(). - if unpack_in_context: - j += 1 - else: - # If there is an unpack in expressions, but not in context, this will - # result in an error later, just do something predictable here. - j += len(tt.items) + + # parse the star argument into a tuple type + mapper = ArgTypeExpander(self.argument_infer_context()) + star_args_type = mapper.parse_star_argument(tt) + items.extend(star_args_type.items) + + # Note: this logic depends on full structure match in tuple_context_matches(). + if unpack_in_context: + j += 1 else: - if allow_precise_tuples and not seen_unpack_in_items: - # Handle (x, *y, z), where y is e.g. tuple[Y, ...]. - if isinstance(tt, Instance) and self.chk.type_is_iterable(tt): - item_type = self.chk.iterable_item_type(tt, e) - mapped = self.chk.named_generic_type("builtins.tuple", [item_type]) - items.append(UnpackType(mapped)) - seen_unpack_in_items = True - continue - # A star expression that's not a Tuple. - # Treat the whole thing as a variable-length tuple. - return self.check_lst_expr(e, "builtins.tuple", "") + # If there is an unpack in expressions, but not in context, this will + # result in an error later, just do something predictable here. + j += len(star_args_type.items) else: if not type_context_items or j >= len(type_context_items): tt = self.accept(item) @@ -5274,14 +5236,15 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type: tt = self.accept(item, type_context_items[j]) j += 1 items.append(tt) - # This is a partial fallback item type. A precise type will be calculated on demand. - fallback_item = AnyType(TypeOfAny.special_form) - result: ProperType = TupleType( - items, self.chk.named_generic_type("builtins.tuple", [fallback_item]) - ) + + # renormalize the items, combining multiple unpacks if needed. + tnf = TupleNormalForm.from_items(items) + tuple_result = tnf.materialize(context=self.argument_infer_context()) + # simplify tuple[*tuple[T], ...] -> tuple[T, ...] + result = tuple_result.simplify() if seen_unpack_in_items: # Return already normalized tuple type just in case. - result = expand_type(result, {}) + return expand_type(result, {}) return result def fast_dict_type(self, e: DictExpr) -> Type | None: @@ -6879,3 +6842,197 @@ def is_type_type_context(context: Type | None) -> bool: if isinstance(context, UnionType): return any(is_type_type_context(item) for item in context.items) return False + + +def _validate_arg_kinds(arg_kinds: list[ArgKind]) -> None: + """Ensure arg_kinds are sorted as expected and only of the expected kinds.""" + assert all( + k in (ARG_POS, ARG_STAR, ARG_NAMED, ARG_STAR2) for k in arg_kinds + ), f"unexpected {arg_kinds=}" + found_named = False + for k in arg_kinds: + if found_named: + assert k in (ARG_NAMED, ARG_STAR2), f"Unexpected arg kind {k} after named" + elif k in (ARG_POS, ARG_STAR): + continue + else: + found_named = True + + +def _use_only_first_valid_arg( + formal_to_actual: list[list[int]], formal_kinds: list[ArgKind] +) -> list[list[int]]: + new_formal_to_actual: list[list[int]] = [] + + for i, actuals in enumerate(formal_to_actual): + formal_kind = formal_kinds[i] + + if formal_kind in (ARG_POS, ARG_OPT, ARG_NAMED, ARG_NAMED_OPT): + # only use the first match, multiple matched indicate errors + new_formal_to_actual.append(actuals[:1]) + else: + new_formal_to_actual.append(actuals) + + return new_formal_to_actual + + +def _validate_formal_to_actual( + formal_to_actual: list[list[int]], actual_kinds: list[ArgKind], formal_kinds: list[ArgKind] +) -> None: + for i, actuals in enumerate(formal_to_actual): + formal_kind = formal_kinds[i] + + if not actuals: + continue + + if len(actuals) > 1: + assert formal_kind in (ARG_STAR, ARG_STAR2), formal_kind + + if formal_kind in (ARG_POS, ARG_OPT): + assert len(actuals) <= 1 + assert all( + actual_kinds[a] in (ARG_POS, ARG_STAR, ARG_NAMED, ARG_STAR2) for a in actuals + ), actual_kinds + + elif formal_kind == ARG_STAR: + assert all(actual_kinds[a] in (ARG_POS, ARG_STAR) for a in actuals) + + elif formal_kind in (ARG_NAMED, ARG_NAMED_OPT): + assert len(actuals) <= 1 + assert all(actual_kinds[a] in (ARG_NAMED, ARG_STAR2) for a in actuals), actual_kinds + + elif formal_kind == ARG_STAR2: + assert all(actual_kinds[a] in (ARG_NAMED, ARG_STAR2) for a in actuals) + + else: + assert False, f"Unexpected formal kind {formal_kind}" + + +def map_actuals_to_star_parameter( + formal_tuple: TupleType, parsed_actuals: list[ParsedActual] +) -> Sequence[Type]: + """Get the expected type for a *args parameter in a function definition. + + Args: + formal_tuple: the formal tuple type of the *args parameter. + parsed_actuals: the actual arguments passed to the function. + + Returns: + expected_types: list containing the expected type for each actual. + + See Also: + Inspired by the algorithm sketched out in + https://github.com/python/mypy/issues/19692#issuecomment-3211743894 + """ + # convert parsed_actuals into a deque for efficient popping from both ends + actual_queue = deque(parsed_actuals) + + # setup pointers and lengths for the formal tuple + formal_unpack_index = formal_tuple.unpack_index + assert formal_unpack_index is not None + formal_prefix_length = len(formal_tuple.prefix) + formal_suffix_length = len(formal_tuple.suffix) + formal_prefix_index = 0 + formal_suffix_index = 0 + + # lists to hold the expected types for each part + expected_prefix_types: list[Type] = [] + expected_middle_types: list[Type] = [] + reversed_suffix_types: list[Type] = [] + + # utility class for accessing very special getitem and slice method with: + # for positive indices >= unpack_index, the item is the variadic iterable type. + # for negative indices <= -unpack_index, the item is the variadic iterable type. + tuple_helper = TupleHelper(formal_tuple) + + # 1. match prefix items left to right + while actual_queue and formal_prefix_index < formal_prefix_length: + # get the actual from the front of the queue + current = actual_queue.popleft() + + if current.kind == ARG_POS: + expected_type = tuple_helper.get_item(formal_tuple, formal_prefix_index) + assert expected_type is not None, "formal_tuple unexpectedly exhausted" + formal_prefix_index += 1 + expected_prefix_types.append(expected_type) + + elif current.kind == ARG_STAR: + p_e = get_proper_type(current.expanded) + assert isinstance(p_e, TupleType) + # check the size of the actual. If it is variadic or larger than the remaining prefix, + # put it back into the queue and break + size = p_e.minimum_length + if p_e.is_variadic or formal_prefix_index + size > formal_prefix_length: + actual_queue.appendleft(current) + break + # otherwise, determine the expected type and append it. + expected_type = tuple_helper.get_slice( + formal_tuple, start=formal_prefix_index, stop=formal_prefix_index + size + ) + formal_prefix_index += size + expected_prefix_types.append(expected_type) + + else: + assert False + + # 2. match suffix items right to left + while actual_queue and formal_suffix_index < formal_suffix_length: + # get the actual from the end of the queue + current = actual_queue.pop() + + if current.kind == ARG_POS: + expected_type = tuple_helper.get_item(formal_tuple, -formal_suffix_index - 1) + assert expected_type is not None, "formal_tuple unexpectedly exhausted" + formal_suffix_index += 1 + reversed_suffix_types.append(expected_type) + + elif current.kind == ARG_STAR: + p_e = get_proper_type(current.expanded) + assert isinstance(p_e, TupleType) + # check the size of the actual. If it is variadic or larger than the remaining suffix, + # put it back into the queue and break + size = p_e.minimum_length + if p_e.is_variadic or formal_suffix_index + size > formal_suffix_length: + actual_queue.append(current) + break + # otherwise, we can consume it + expected_type = tuple_helper.get_slice( + formal_tuple, + start=-formal_suffix_index - size, + # since negative integer zero is not a thing we need to use None in this case. + stop=None if formal_suffix_index == 0 else -formal_suffix_index, + ) + formal_suffix_index += size + reversed_suffix_types.append(expected_type) + + else: + assert False + + # 3. match the remaining items against the unpack part, left to right + while actual_queue: + current = actual_queue.popleft() + + if current.kind == ARG_POS: + expected_type = tuple_helper.get_item(formal_tuple, formal_prefix_index) + assert expected_type is not None, "formal_tuple unexpectedly exhausted" + expected_middle_types.append(expected_type) + formal_prefix_index += 1 + + elif current.kind == ARG_STAR: + p_e = get_proper_type(current.expanded) + assert isinstance(p_e, TupleType) + prefix_size = len(p_e.prefix) + suffix_size = len(p_e.suffix) + expected_type = tuple_helper.get_slice( + formal_tuple, + start=formal_prefix_index, + stop=None if formal_suffix_index == 0 else -formal_suffix_index, + ) + formal_prefix_index += prefix_size + formal_suffix_index += suffix_size + expected_middle_types.append(expected_type) + + else: + assert False + + return expected_prefix_types + expected_middle_types + reversed_suffix_types[::-1] diff --git a/mypy/constraints.py b/mypy/constraints.py index a8fa114e6029..78c0c0abe651 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -3,7 +3,8 @@ from __future__ import annotations from collections.abc import Iterable, Sequence -from typing import TYPE_CHECKING, Final, TypeGuard, cast +from typing import TYPE_CHECKING, Final, NamedTuple, cast +from typing_extensions import TypeGuard import mypy.subtypes import mypy.typeops @@ -11,6 +12,8 @@ from mypy.erasetype import erase_typevars from mypy.maptype import map_instance_to_supertype from mypy.nodes import ( + ARG_NAMED, + ARG_NAMED_OPT, ARG_OPT, ARG_POS, ARG_STAR, @@ -63,7 +66,7 @@ from mypy.typestate import type_state if TYPE_CHECKING: - from mypy.infer import ArgumentInferContext + from mypy.infer import ArgumentInferContext, TupleInstanceType SUBTYPE_OF: Final = 0 SUPERTYPE_OF: Final = 1 @@ -120,6 +123,7 @@ def infer_constraints_for_callable( """ constraints: list[Constraint] = [] mapper = ArgTypeExpander(context) + arg_names = arg_names or [None] * len(arg_types) param_spec = callee.param_spec() param_spec_arg_types = [] @@ -136,120 +140,95 @@ def infer_constraints_for_callable( break for i, actuals in enumerate(formal_to_actual): - if isinstance(callee.arg_types[i], UnpackType): - unpack_type = callee.arg_types[i] - assert isinstance(unpack_type, UnpackType) - - # In this case we are binding all the actuals to *args, - # and we want a constraint that the typevar tuple being unpacked - # is equal to a type list of all the actuals. - actual_types = [] - - unpacked_type = get_proper_type(unpack_type.type) - if isinstance(unpacked_type, TypeVarTupleType): - tuple_instance = unpacked_type.tuple_fallback - elif isinstance(unpacked_type, TupleType): - tuple_instance = unpacked_type.partial_fallback - else: - assert False, "mypy bug: unhandled constraint inference case" - - for actual in actuals: - actual_arg_type = arg_types[actual] - if actual_arg_type is None: - continue + formal_kind = callee.arg_kinds[i] + formal_name = callee.arg_names[i] + formal_type = get_proper_type(callee.arg_types[i]) + + # 1. expand all the actual types. + parsed_actuals = [ + ParsedActual( + id=a, + type=actual_type, + kind=arg_kinds[a], + name=arg_names[a], + expanded=mapper.expand_actual_type( + actual_type, arg_kinds[a], formal_name, formal_kind + ), + ) + for a in actuals + if (actual_type := arg_types[a]) is not None + ] + + # 2. depending on the formal kind, we can infer constraints. + if formal_kind in (ARG_POS, ARG_OPT, ARG_NAMED, ARG_NAMED_OPT): + # There should be at most one actual mapped to a formal of these kinds. + # Multiple actuals indicate a TOO_MANY_ARGUMENTS error, which is handled + # elsewhere. Therefore, for the purpose of constraints only consider the first actual. + for parsed_actual in parsed_actuals: + c = infer_constraints(formal_type, parsed_actual.expanded, SUPERTYPE_OF) + constraints.extend(c) + break # only consider the first actual, multiple actuals are an error + + elif param_spec and not incomplete_star_mapping: + assert formal_kind in (ARG_STAR, ARG_STAR2) + assert isinstance(formal_type, ParamSpecType) + # If actual arguments are mapped to ParamSpec type, we can't infer individual + # constraints, instead store them and infer single constraint at the end. + # It is impossible to map actual kind to formal kind, so use some heuristic. + # This inference is used as a fallback, so relying on heuristic should be OK. + for parsed_actual in parsed_actuals: + actual_kind = parsed_actual.kind + actual_name = parsed_actual.name + expanded_type = parsed_actual.expanded + + if actual_kind in (ARG_POS, ARG_NAMED): + param_spec_arg_types.append(expanded_type) + param_spec_arg_kinds.append(ARG_POS) + param_spec_arg_names.append(actual_name) + elif actual_kind == ARG_STAR: + # TODO: shouldn't this re-normalization be done in the constructor of types.Parameters? + param_spec_arg_types.append(mapper.unparse_star_parameter(expanded_type)) + param_spec_arg_kinds.append(actual_kind) + param_spec_arg_names.append(actual_name) + elif actual_kind == ARG_STAR2: + param_spec_arg_types.append(expanded_type) + param_spec_arg_kinds.append(actual_kind) + param_spec_arg_names.append(actual_name) + else: + # ARG_OPT/ARG_NAMED_OPT is not possible for actuals + assert False, f"Unexpected actual kind {actual_kind}" + + elif formal_kind == ARG_STAR: + # parse the formal type into a tuple + formal_tuple = mapper.parse_star_parameter(formal_type) + + # combine all the actuals into a single tuple type + items: list[Type] = [] + for parsed_actual in parsed_actuals: + actual_kind = parsed_actual.kind + expanded_type = parsed_actual.expanded + + if actual_kind == ARG_STAR: + p_e = get_proper_type(expanded_type) + assert isinstance(p_e, TupleType) + items.extend(p_e.items) + elif actual_kind == ARG_POS: + items.append(expanded_type) + else: + assert False, f"Unexpected actual kind {actual_kind}" + actual_tuple = context.make_tuple_type(items) - expanded_actual = mapper.expand_actual_type( - actual_arg_type, - arg_kinds[actual], - callee.arg_names[i], - callee.arg_kinds[i], - allow_unpack=True, - ) + # infer constraints from these two tuples + c = infer_constraints(formal_tuple, actual_tuple, SUPERTYPE_OF) + constraints.extend(c) - if arg_kinds[actual] != ARG_STAR or isinstance( - get_proper_type(actual_arg_type), TupleType - ): - actual_types.append(expanded_actual) - else: - # If we are expanding an iterable inside * actual, append a homogeneous item instead - actual_types.append( - UnpackType(tuple_instance.copy_modified(args=[expanded_actual])) - ) + elif formal_kind == ARG_STAR2: + for parsed_actual in parsed_actuals: + c = infer_constraints(formal_type, parsed_actual.expanded, SUPERTYPE_OF) + constraints.extend(c) - if isinstance(unpacked_type, TypeVarTupleType): - constraints.append( - Constraint( - unpacked_type, - SUPERTYPE_OF, - TupleType(actual_types, unpacked_type.tuple_fallback), - ) - ) - elif isinstance(unpacked_type, TupleType): - # Prefixes get converted to positional args, so technically the only case we - # should have here is like Tuple[Unpack[Ts], Y1, Y2, Y3]. If this turns out - # not to hold we can always handle the prefixes too. - inner_unpack = unpacked_type.items[0] - assert isinstance(inner_unpack, UnpackType) - inner_unpacked_type = get_proper_type(inner_unpack.type) - suffix_len = len(unpacked_type.items) - 1 - if isinstance(inner_unpacked_type, TypeVarTupleType): - # Variadic item can be either *Ts... - constraints.append( - Constraint( - inner_unpacked_type, - SUPERTYPE_OF, - TupleType( - actual_types[:-suffix_len], inner_unpacked_type.tuple_fallback - ), - ) - ) - else: - # ...or it can be a homogeneous tuple. - assert ( - isinstance(inner_unpacked_type, Instance) - and inner_unpacked_type.type.fullname == "builtins.tuple" - ) - for at in actual_types[:-suffix_len]: - constraints.extend( - infer_constraints(inner_unpacked_type.args[0], at, SUPERTYPE_OF) - ) - # Now handle the suffix (if any). - if suffix_len: - for tt, at in zip(unpacked_type.items[1:], actual_types[-suffix_len:]): - constraints.extend(infer_constraints(tt, at, SUPERTYPE_OF)) - else: - assert False, "mypy bug: unhandled constraint inference case" else: - for actual in actuals: - actual_arg_type = arg_types[actual] - if actual_arg_type is None: - continue - - if param_spec and callee.arg_kinds[i] in (ARG_STAR, ARG_STAR2): - # If actual arguments are mapped to ParamSpec type, we can't infer individual - # constraints, instead store them and infer single constraint at the end. - # It is impossible to map actual kind to formal kind, so use some heuristic. - # This inference is used as a fallback, so relying on heuristic should be OK. - if not incomplete_star_mapping: - param_spec_arg_types.append( - mapper.expand_actual_type( - actual_arg_type, arg_kinds[actual], None, arg_kinds[actual] - ) - ) - actual_kind = arg_kinds[actual] - param_spec_arg_kinds.append( - ARG_POS if actual_kind not in (ARG_STAR, ARG_STAR2) else actual_kind - ) - param_spec_arg_names.append(arg_names[actual] if arg_names else None) - else: - actual_type = mapper.expand_actual_type( - actual_arg_type, - arg_kinds[actual], - callee.arg_names[i], - callee.arg_kinds[i], - ) - c = infer_constraints(callee.arg_types[i], actual_type, SUPERTYPE_OF) - constraints.extend(c) + assert False, f"Unexpected formal kind {formal_kind}" if ( param_spec and not any(c.type_var == param_spec.id for c in constraints) @@ -735,7 +714,15 @@ def visit_param_spec(self, template: ParamSpecType) -> list[Constraint]: return [] def visit_type_var_tuple(self, template: TypeVarTupleType) -> list[Constraint]: - raise NotImplementedError + actual = self.actual + if isinstance(actual, TupleType): + actual = actual.simplify() + if isinstance(actual, (TupleType, TypeVarTupleType, ParamSpecType)) or ( + isinstance(actual, Instance) and actual.type.fullname == "builtins.tuple" + ): + # Ts can be bounded by other Ts, TupleType or variadic tuple + return [Constraint(template, self.direction, actual)] + raise NotImplementedError("Cannot infer TypeVarTupleType from " + str(actual)) def visit_unpack_type(self, template: UnpackType) -> list[Constraint]: raise RuntimeError("Mypy bug: unpack should be handled at a higher level.") @@ -998,6 +985,7 @@ def visit_instance(self, template: Instance) -> list[Constraint]: return res if res: return res + del res # unscope as not used below. if isinstance(actual, AnyType): return self.infer_against_any(template.args, actual) @@ -1006,21 +994,30 @@ def visit_instance(self, template: Instance) -> list[Constraint]: and is_named_instance(template, TUPLE_LIKE_INSTANCE_NAMES) and self.direction == SUPERTYPE_OF ): - for item in actual.items: + # infer constraints for (template=tuple[T, ...]) :> (actual=tuple[T1, ..., Tn]) + # that is T :> T1 and T :> T2, ..., T :> Tn + # Note: We do not infer any constraints for tuple[T, ...] :> tuple[()] + generic_type = template.args[0] + constraints: list[Constraint] = [] + for item in actual.flattened_items: if isinstance(item, UnpackType): unpacked = get_proper_type(item.type) if isinstance(unpacked, TypeVarTupleType): - # Cannot infer anything for T from [T, ...] <: *Ts - continue - assert ( + # tuple[T, ...] :> tuple[*Ts] implies T :> Union[*Ts] + # Since Union[*Ts] is currently not available, use Any instead. + item = AnyType(TypeOfAny.implementation_artifact) + elif ( isinstance(unpacked, Instance) and unpacked.type.fullname == "builtins.tuple" - ) - item = unpacked.args[0] - cb = infer_constraints(template.args[0], item, SUPERTYPE_OF) - res.extend(cb) - return res - elif isinstance(actual, TupleType) and self.direction == SUPERTYPE_OF: + ): + item = unpacked.args[0] + else: + raise TypeError(f"Unexpected unpack type {unpacked}") + constraints += infer_constraints(generic_type, item, self.direction) + return constraints + elif isinstance(actual, TupleType): + # NOTE: tuple[T, ...] <: tuple[A, B, C] has no proper solution, + # but we still infer T <: A | B | C return infer_constraints(template, mypy.typeops.tuple_fallback(actual), self.direction) elif isinstance(actual, TypeVarType): if not actual.values and not actual.id.is_meta_var(): @@ -1029,6 +1026,15 @@ def visit_instance(self, template: Instance) -> list[Constraint]: elif isinstance(actual, ParamSpecType): return infer_constraints(template, actual.upper_bound, self.direction) elif isinstance(actual, TypeVarTupleType): + # TODO: also consider case when template is a subclass of builtins.tuple. + if template.type.fullname == "builtins.tuple": + # infer constraints for tuple[T, ...] vs Ts + # tuple[T, ...] :> Ts => T :> Union[*Ts] + # tuple[T, ...] <: Ts => T <: Intersection[*Ts] + # Since neither of these are currently supported, we use Any as a fallback. + return infer_constraints( + template.args[0], AnyType(TypeOfAny.implementation_artifact), self.direction + ) raise NotImplementedError else: return [] @@ -1247,102 +1253,253 @@ def infer_against_overloaded( return infer_constraints(template, item, self.direction) def visit_tuple_type(self, template: TupleType) -> list[Constraint]: + # NOTE: Expects a normalized TupleType, i.e. one with at most one Unpack, + # and the Unpack should be flattened, i.e. only contain a TypeVarTuple or Tuple Instance. + from mypy.tuple_normal_form import TupleHelper + actual = self.actual - unpack_index = find_unpack_in_list(template.items) - is_varlength_tuple = ( - isinstance(actual, Instance) and actual.type.fullname == "builtins.tuple" - ) + tuple_helper = TupleHelper(template) - if isinstance(actual, TupleType) or is_varlength_tuple: - res: list[Constraint] = [] - if unpack_index is not None: - if is_varlength_tuple: - # Variadic tuple can be only a supertype of a tuple type, but even if - # direction is opposite, inferring something may give better error messages. - unpack_type = template.items[unpack_index] - assert isinstance(unpack_type, UnpackType) - unpacked_type = get_proper_type(unpack_type.type) - if isinstance(unpacked_type, TypeVarTupleType): - res = [ - Constraint(type_var=unpacked_type, op=self.direction, target=actual) - ] - else: - assert ( - isinstance(unpacked_type, Instance) - and unpacked_type.type.fullname == "builtins.tuple" + if tuple_helper.is_tuple_instance_subtype(actual): + # reinterpret tuple[T, ...] as tuple[*tuple[T, ...]] + as_tuple_instance = tuple_helper.as_tuple_instance_type(actual) + actual = TupleType([UnpackType(as_tuple_instance)], fallback=as_tuple_instance) + + if isinstance(actual, TupleType): + constraints: list[Constraint] = [] + + if ( + actual.partial_fallback.type.is_named_tuple + and template.partial_fallback.type.is_named_tuple + ): + # For named tuples we additionally consider type-specific constraints. + constraints += infer_constraints( + template.partial_fallback, actual.partial_fallback, self.direction + ) + + # setup: consider prefix-, unpack- and suffix- part of each tuple + template_prefix = template.prefix + template_suffix = template.suffix + template_unpack = template.unpack + template_prefix_size = len(template_prefix) + template_suffix_size = len(template_suffix) + template_size = template_prefix_size + template_suffix_size + + actual_prefix = actual.prefix + actual_suffix = actual.suffix + actual_unpack = actual.unpack + actual_prefix_size = len(actual_prefix) + actual_suffix_size = len(actual_suffix) + actual_size = actual_prefix_size + actual_suffix_size + # a tuple[T, ...] that can be used as a fallback for the unpack part + actual_variadic_fallback: TupleInstanceType | None = ( + None + if actual_unpack is None + else tuple_helper.get_variadic_fallback(actual_unpack) + ) + actual_variadic_item_type: Type | None = ( + None if actual_variadic_fallback is None else actual_variadic_fallback.args[0] + ) + + # Consider both tuples in Tuple Normal Form, i.e. + # template: tuple[P1, ..., Pn, *Us?, S1, ..., Sm] + # actual: tuple[A1, ..., Ak, *Vs?, B1, ..., Bl] + # since either can be variadic or not, there are four cases to consider: + # 1. both template and actual are variadic + # 2. template is variadic, but actual is not variadic + # 3. template is not variadic, but actual is variadic + # 4. neither template nor actual are variadic + + # Case 1. neither is variadic. + # template: tuple[T1, ..., Tm] + # actual: tuple[A1, ..., An] + if actual_unpack is None and template_unpack is None: + assert not actual_suffix and not template_suffix + if len(template_prefix) == len(actual_prefix): + for t_item, a_item in zip(template_prefix, actual_prefix): + constraints += infer_constraints(t_item, a_item, self.direction) + # otherwise, no constraints can be inferred as the tuples are incompatible. + + # Case 2. template is variadic, actual is not variadic. + # template: tuple[P1, ..., Pk, *Us, S1, ..., Sm] + # actual: tuple[A1, ..., An] + elif actual_unpack is None and template_unpack is not None: + assert not actual_suffix + actual_size = len(actual_prefix) + + template_prefix_size = len(template_prefix) + template_suffix_size = len(template_suffix) + + # there are enough actual items to match template prefix and suffix + if template_size <= actual_size: + # match prefix and suffix items one-to-one + # TODO: use zip with strict=True + for t_item, a_item in zip( + template_prefix, actual_prefix[:template_prefix_size] + ): + constraints += infer_constraints(t_item, a_item, self.direction) + for t_item, a_item in zip( + template_suffix, actual_prefix[actual_size - template_suffix_size :] + ): + constraints += infer_constraints(t_item, a_item, self.direction) + + # match the unpack items against the remaining slice of actual items + remaining_items = actual_prefix[ + template_prefix_size : actual_size - template_suffix_size + ] + remaining_actual = tuple_helper.make_tuple_type(remaining_items) + constraints += infer_constraints( + template_unpack.type, remaining_actual, self.direction + ) + # otherwise, no constraints can be inferred since the tuples are incompatible. + # (actual has too few items) + + # Case 3. template is not variadic, actual is variadic. + # template: tuple[T1, ..., Tm] + # actual: tuple[A1, ..., Ak, *Vs, B1, ..., Bl] + elif actual_unpack is not None and template_unpack is None: + assert not template_suffix + assert actual_variadic_item_type is not None + assert actual_variadic_fallback is not None + + # fixed size tuple can't be a supertype of a variable length tuple + if self.direction == SUPERTYPE_OF: + return [] + + if template_size >= actual_size: + # match prefix items one-to-one + for t_item, a_item in zip(template_prefix[:actual_prefix_size], actual_prefix): + constraints += infer_constraints(t_item, a_item, self.direction) + + # match suffix items one-to-one + for t_item, a_item in zip( + template_prefix[template_size - actual_suffix_size :], actual_suffix + ): + constraints += infer_constraints(t_item, a_item, self.direction) + + # match the remaining items to the actual generic type + for t_item in template_prefix[ + actual_prefix_size : template_size - actual_suffix_size + ]: + constraints += infer_constraints( + t_item, actual_variadic_item_type, self.direction ) - res = infer_constraints(unpacked_type, actual, self.direction) - assert isinstance(actual, Instance) # ensured by is_varlength_tuple == True - for i, ti in enumerate(template.items): - if i == unpack_index: - # This one we just handled above. - continue - # For Tuple[T, *Ts, S] <: tuple[X, ...] infer also T <: X and S <: X. - res.extend(infer_constraints(ti, actual.args[0], self.direction)) - return res - else: - assert isinstance(actual, TupleType) - unpack_constraints = build_constraints_for_simple_unpack( - template.items, actual.items, self.direction + + # otherwise, no constraints can be inferred since the tuples are incompatible. + # (actual has too many items) + + # Case 4. both are variadic. + # template: tuple[P1, ..., Pk, *Us, S1, ..., Sm] + # actual: tuple[A1, ..., Ak, *Vs, B1, ..., Bl] + elif actual_unpack is not None and template_unpack is not None: + assert actual_variadic_item_type is not None + assert actual_variadic_fallback is not None + + # match prefix items one-by-one until one of the tuples runs out + for t_item, a_item in zip(template_prefix, actual_prefix): + constraints += infer_constraints(t_item, a_item, self.direction) + + # match suffix items one-by-one until one of the tuples runs out + for t_item, a_item in zip(reversed(template_suffix), reversed(actual_suffix)): + constraints += infer_constraints(t_item, a_item, self.direction) + + template_prefix_exhausted = template_prefix_size <= actual_prefix_size + template_suffix_exhausted = template_suffix_size <= actual_suffix_size + actual_prefix_exhausted = actual_prefix_size <= template_prefix_size + actual_suffix_exhausted = actual_suffix_size <= template_suffix_size + + # Now, there are four sub-cases to consider: + + # Case 4.1 template prefix and suffix are both exhausted + # template: tuple[*Us] + # actual: tuple[A1, ..., Ak, *Vs, B1, ..., Bl] + if template_prefix_exhausted and template_suffix_exhausted: + # We can match Us directly to the remaining actual items + items = [ + *actual_prefix[template_prefix_size:], + actual_unpack, + *actual_suffix[: actual_suffix_size - template_suffix_size], + ] + remaining_actual = tuple_helper.make_tuple_type(items) + constraints += infer_constraints( + template_unpack.type, remaining_actual.simplify(), self.direction + ) + + # case 4.2 template prefix exhausted, suffix not exhausted (m>0) + # template: tuple[*Us, S1, ..., Sm] + # actual: tuple[A1, ..., Ak, *Vs] + elif template_prefix_exhausted and not template_suffix_exhausted: + # Here, there are potentially infinitely many solutions. + # Moreover, TVTs do not support slicing or indexing. + # We resolve it by using a tuple[T, ...] fallback for *Vs. + assert actual_suffix_exhausted + + # match *Us to [A1, ..., Ak, *Vs], using actual_variadic_fallback for *Vs + remaining_actual_prefix = actual_prefix[template_prefix_size:] + remaining_actual = tuple_helper.make_tuple_type( + [*remaining_actual_prefix, UnpackType(actual_variadic_fallback)] ) - actual_items: tuple[Type, ...] = () - template_items: tuple[Type, ...] = () - res.extend(unpack_constraints) - elif isinstance(actual, TupleType): - a_unpack_index = find_unpack_in_list(actual.items) - if a_unpack_index is not None: - # The case where template tuple doesn't have an unpack, but actual tuple - # has an unpack. We can infer something if actual unpack is a variadic tuple. - # Tuple[T, S, U] <: tuple[X, *tuple[Y, ...], Z] => T <: X, S <: Y, U <: Z. - a_unpack = actual.items[a_unpack_index] - assert isinstance(a_unpack, UnpackType) - a_unpacked = get_proper_type(a_unpack.type) - if len(actual.items) + 1 <= len(template.items): - a_prefix_len = a_unpack_index - a_suffix_len = len(actual.items) - a_unpack_index - 1 - t_prefix, t_middle, t_suffix = split_with_prefix_and_suffix( - tuple(template.items), a_prefix_len, a_suffix_len + constraints += infer_constraints( + template_unpack.type, remaining_actual.simplify(), self.direction + ) + + # match S1, ..., Sm to *Vs, using actual_variadic_item_type for *Vs + for item in template_suffix[: template_suffix_size - actual_suffix_size]: + constraints += infer_constraints( + item, actual_variadic_item_type, self.direction ) - actual_items = tuple(actual.items[:a_prefix_len]) - if a_suffix_len: - actual_items += tuple(actual.items[-a_suffix_len:]) - template_items = t_prefix + t_suffix - if isinstance(a_unpacked, Instance): - assert a_unpacked.type.fullname == "builtins.tuple" - for tm in t_middle: - res.extend( - infer_constraints(tm, a_unpacked.args[0], self.direction) - ) - else: - actual_items = () - template_items = () - else: - actual_items = tuple(actual.items) - template_items = tuple(template.items) - else: - return res - # Cases above will return if actual wasn't a TupleType. - assert isinstance(actual, TupleType) - if len(actual_items) == len(template_items): - if ( - actual.partial_fallback.type.is_named_tuple - and template.partial_fallback.type.is_named_tuple - ): - # For named tuples using just the fallbacks usually gives better results. - return res + infer_constraints( - template.partial_fallback, actual.partial_fallback, self.direction + # case 4.3: template suffix exhausted, prefix not exhausted (k>0) + # template: tuple[P1, ..., Pk, *Us] + # actual: tuple[*Vs, B1, ..., Bl] + elif not template_prefix_exhausted and template_suffix_exhausted: + # Here, there are potentially infinitely many solutions. + # Moreover, TVTs do not support slicing or indexing. + # We resolve it by using a tuple[T, ...] fallback for *Vs. + assert actual_prefix_exhausted + + # match [P1, ..., Pk] to [*Vs], using actual_variadic_item_type for *Vs + for item in template_prefix[actual_prefix_size:]: + constraints += infer_constraints( + item, actual_variadic_item_type, self.direction + ) + + # match [*Us] to [*Vs, B1, ..., Bl], using actual_variadic_fallback for *Vs + remaining_actual_suffix = actual_suffix[ + : actual_suffix_size - template_suffix_size + ] + remaining_actual = tuple_helper.make_tuple_type( + [UnpackType(actual_variadic_fallback), *remaining_actual_suffix] ) - for i in range(len(template_items)): - res.extend( - infer_constraints(template_items[i], actual_items[i], self.direction) + constraints += infer_constraints( + template_unpack.type, remaining_actual.simplify(), self.direction ) - res.extend( - infer_constraints( - template.partial_fallback, actual.partial_fallback, self.direction - ) - ) - return res + + # Case 4.4: neither template prefix nor template suffix are exhausted + # template: tuple[P1, ..., Pk, *Us, S1, ..., Sm] + # actual: tuple[*Vs] + elif not template_prefix_exhausted and not template_suffix_exhausted: + # Use actual_variadic_fallback here, since TVTs do not support slicing or indexing + assert actual_prefix_exhausted and actual_suffix_exhausted + + for t_item in template_prefix[actual_prefix_size:]: + constraints += infer_constraints( + t_item, actual_variadic_item_type, self.direction + ) + for t_item in template_suffix[: template_suffix_size - actual_suffix_size]: + constraints += infer_constraints( + t_item, actual_variadic_item_type, self.direction + ) + constraints += infer_constraints( + template_unpack.type, actual_variadic_fallback, self.direction + ) + + else: + assert False + else: + assert False + + return constraints elif isinstance(actual, AnyType): return self.infer_against_any(template.items, actual) else: @@ -1710,3 +1867,13 @@ def filter_imprecise_kinds(cs: list[Constraint]) -> list[Constraint]: if not isinstance(c.target, Parameters) or not c.target.imprecise_arg_kinds: new_cs.append(c) return new_cs + + +class ParsedActual(NamedTuple): + r"""Simple record type to hold parsed actual argument in a function call.""" + + id: int + kind: ArgKind + type: Type + expanded: Type + name: str | None = None diff --git a/mypy/expandtype.py b/mypy/expandtype.py index 7f95b2e25320..d77761dc9aa7 100644 --- a/mypy/expandtype.py +++ b/mypy/expandtype.py @@ -396,7 +396,7 @@ def expand_unpack(self, t: UnpackType) -> list[Type]: elif ( isinstance(repl, Instance) and repl.type.fullname == "builtins.tuple" - or isinstance(repl, TypeVarTupleType) + or isinstance(repl, (TypeVarTupleType, ParamSpecType)) ): return [UnpackType(typ=repl)] elif isinstance(repl, (AnyType, UninhabitedType)): diff --git a/mypy/infer.py b/mypy/infer.py index cdc43797d3b1..2d3083b90a67 100644 --- a/mypy/infer.py +++ b/mypy/infer.py @@ -3,7 +3,8 @@ from __future__ import annotations from collections.abc import Sequence -from typing import NamedTuple +from typing import NamedTuple, NewType, cast +from typing_extensions import TypeIs from mypy.constraints import ( SUBTYPE_OF, @@ -11,9 +12,35 @@ infer_constraints, infer_constraints_for_callable, ) -from mypy.nodes import ArgKind +from mypy.nodes import ARG_POS, ArgKind, TypeInfo from mypy.solve import solve_constraints -from mypy.types import CallableType, Instance, Type, TypeVarLikeType +from mypy.tuple_normal_form import TupleNormalForm +from mypy.typeops import make_simplified_union +from mypy.types import ( + AnyType, + CallableType, + Instance, + ParamSpecType, + ProperType, + TupleType, + Type, + TypeList, + TypeOfAny, + TypeVarId, + TypeVarLikeType, + TypeVarTupleType, + TypeVarType, + UninhabitedType, + UnionType, + UnpackType, + flatten_nested_tuples, + get_proper_type, +) + +IterableType = NewType("IterableType", Instance) +"""Represents an instance of `Iterable[T]`.""" +TupleInstanceType = NewType("TupleInstanceType", Instance) +"""Represents an instance of `tuple[T, ...]`.""" class ArgumentInferContext(NamedTuple): @@ -28,6 +55,319 @@ class ArgumentInferContext(NamedTuple): mapping_type: Instance iterable_type: Instance + function_type: Instance + tuple_typeinfo: TypeInfo + + @property + def fallback_tuple(self) -> Instance: + r"""Canonical fallback tuple type tuple[Any, ...].""" + # NOTE: This must use ``TypeOfAny.special_form`` and not ``TypeOfAny.from_omitted_generics``, + # otherwise this leads to errors in dmypy SuggestionEngine. + return Instance(self.tuple_typeinfo, [AnyType(TypeOfAny.special_form)]) + + def is_iterable(self, typ: Type) -> bool: + """Check if the type is an iterable, i.e. implements the Iterable Protocol.""" + from mypy.subtypes import is_subtype + + return is_subtype(typ, self.iterable_type) + + def is_iterable_instance_type(self, typ: Type) -> TypeIs[IterableType]: + """Check if the type is an Iterable[T].""" + p_t = get_proper_type(typ) + return isinstance(p_t, Instance) and p_t.type == self.iterable_type.type + + def is_tuple_instance_type(self, typ: Type) -> TypeIs[TupleInstanceType]: + """Check if the type is a tuple instance, i.e. tuple[T, ...].""" + p_t = get_proper_type(typ) + return isinstance(p_t, Instance) and p_t.type == self.tuple_typeinfo + + def make_tuple_instance_type(self, arg: Type) -> TupleInstanceType: + """Create a TupleInstance type with the given argument type.""" + value = Instance(self.tuple_typeinfo, [arg]) + return cast(TupleInstanceType, value) + + def make_iterable_instance_type(self, arg: Type) -> IterableType: + value = Instance(self.iterable_type.type, [arg]) + return cast(IterableType, value) + + def make_tuple_type(self, items: Sequence[Type], /) -> TupleType: + r"""Create a proper TupleType from the given item types.""" + tnf = TupleNormalForm.from_items(items) + return tnf.materialize(context=self) + + def materialize_tnf(self, tnf: TupleNormalForm) -> TupleType: + r"""Construct an actual TupleType from a TupleNormalForm. + + Combines all members of the variadic part into a single tuple[T, ...] type. + This creates an upper bound for the original `star_args` argument. + + Pays special attention to the variadic part, which may contain unexpected + `UnpackType` members, namely `UnionType[TypeList]`. + """ + + # parse the variadic part. UninhabitedType indicated no variadic part. + # AnyType indicates we could not properly parse the variadic part. + parsed_variadic_part = self._parse_variadic_type(tnf.variadic) + + # check whether the unpack is considered empty + unpacked = get_proper_type(parsed_variadic_part.type) + is_empty_unpack = isinstance(unpacked, UninhabitedType) + + if is_empty_unpack: + assert not tnf.suffix, f"Failed to correctly parse TupleNormalForm: {tnf}" + return TupleType([*tnf.prefix], fallback=self.fallback_tuple) + + return TupleType( + [*tnf.prefix, parsed_variadic_part, *tnf.suffix], fallback=self.fallback_tuple + ) + + def as_iterable_type(self, typ: Type) -> IterableType | AnyType: + r"""Reinterpret a type as Iterable[T], or return AnyType if not possible. + + This function specially handles certain types like UnionType, TupleType, and UnpackType. + Otherwise, the upcasting is performed using the solver. + """ + p_t = get_proper_type(typ) + if self.is_iterable_instance_type(p_t) or isinstance(p_t, AnyType): + return p_t + elif isinstance(p_t, UnionType): + # If the type is a union, map each item to the iterable supertype. + # the return the combined iterable type Iterable[A] | Iterable[B] -> Iterable[A | B] + converted_types = [self.as_iterable_type(get_proper_type(item)) for item in p_t.items] + + if any(not self.is_iterable_instance_type(it) for it in converted_types): + # if any item could not be interpreted as Iterable[T], we return AnyType + return AnyType(TypeOfAny.from_error) + else: + # all items are iterable, return Iterable[T₁ | T₂ | ... | Tₙ] + iterable_types = cast("list[IterableType]", converted_types) + arg = make_simplified_union([it.args[0] for it in iterable_types]) + return self.make_iterable_instance_type(arg) + elif isinstance(p_t, TupleType): + # maps tuple[A, B, C] -> Iterable[A | B | C] + # note: proper_elements may contain UnpackType, for instance with + # tuple[None, *tuple[None, ...]].. + proper_elements = [get_proper_type(t) for t in flatten_nested_tuples(p_t.items)] + args: list[Type] = [] + for p_e in proper_elements: + if isinstance(p_e, UnpackType): + r = self.as_iterable_type(p_e) + if self.is_iterable_instance_type(r): + args.append(r.args[0]) + else: + # this *should* never happen, since UnpackType should + # only contain TypeVarTuple or a variable length tuple. + # However, we could get an `AnyType(TypeOfAny.from_error)` + # if for some reason the solver was triggered and failed. + args.append(r) + else: + args.append(p_e) + return self.make_iterable_instance_type(make_simplified_union(args)) + elif isinstance(p_t, UnpackType): + return self.as_iterable_type(p_t.type) + elif isinstance(p_t, TypeVarType): + # for a regular TypeVar, check the upper bound. + return self.as_iterable_type(p_t.upper_bound) + elif isinstance(p_t, TypeVarTupleType): + # TVT -> tuple[T₁, T₂, ..., Tₙ] + # since this is always iterable, but the variables are not known, + # we return Iterable[Any] + error_type = AnyType(TypeOfAny.from_error) + return self.make_iterable_instance_type(error_type) + elif self.is_iterable(p_t): + # TODO: add a 'fast path' (needs measurement) that uses the map_instance_to_supertype + # mechanism? (Only if it works: gh-19662) + return self._solve_as_iterable(p_t) + + # failure case, return AnyType + return AnyType(TypeOfAny.from_error) + + def _solve_as_iterable(self, typ: Type, /) -> IterableType | AnyType: + r"""Use the solver to cast a type as Iterable[T]. + + Returns `AnyType` if solving fails. + """ + from mypy.constraints import infer_constraints_for_callable + from mypy.solve import solve_constraints + + # We first create an upcast function: + # def [T] (Iterable[T]) -> Iterable[T]: ... + # and then solve for T, given the input type as the argument. + T = TypeVarType( + "T", + "T", + TypeVarId(-1), + values=[], + upper_bound=AnyType(TypeOfAny.from_omitted_generics), + default=AnyType(TypeOfAny.from_omitted_generics), + ) + target = self.make_iterable_instance_type(T) + upcast_callable = CallableType( + variables=[T], + arg_types=[target], + arg_kinds=[ARG_POS], + arg_names=[None], + ret_type=target, + fallback=self.function_type, + ) + constraints = infer_constraints_for_callable( + upcast_callable, [typ], [ARG_POS], [None], [[0]], self + ) + + (sol,), _ = solve_constraints([T], constraints) + + if sol is None: # solving failed, return AnyType fallback + error_type = AnyType(TypeOfAny.from_error) + return self.make_iterable_instance_type(error_type) + return self.make_iterable_instance_type(sol) + + def _parse_variadic_type(self, typ: UnpackType, /) -> UnpackType: + r"""Parse the (dirty) UnpackType of a TupleNormalForm. + + A TupleNormalForm's unpack may contain the following unexpected types: + + 1. UninhabitedType: indicates no variadic part + 2. TypeList: indicates concatenation of multiple variadic parts + 3. UnionType: indicates union of multiple variadic parts + + After processing with this function, the result is guaranteed to be one of: + + 1. UninhabitedType: indicates no variadic part + 2. regular UnpackType content. + """ + + unpacked = get_proper_type(typ.type) + + if isinstance(unpacked, UninhabitedType): + # this is used to indicate no variadic part + return typ + + if isinstance(unpacked, TypeList): + return self._materialize_variadic_concatenation(unpacked) + + elif isinstance(unpacked, UnionType): + return self._materialize_variadic_union(unpacked) + + elif isinstance( + unpacked, (ParamSpecType, TypeVarTupleType) + ) or self.is_tuple_instance_type(unpacked): + # already a proper element. Just return it. + return typ + + # otherwise, cast to Iterable[T] using the solver, and then return tuple[T, ...] + r = self.as_iterable_type(unpacked) + if isinstance(r, AnyType): + return UnpackType(self.make_tuple_instance_type(r)) + return UnpackType(self.make_tuple_instance_type(r.args[0])) + + def _materialize_variadic_concatenation(self, unpacked: TypeList) -> UnpackType: + """Convert a concatenation of UnpackType / items into a single UnpackType.""" + parsed_items: list[ProperType] = [] + for proper_item in map(get_proper_type, unpacked.items): + if isinstance(proper_item, UnpackType): + # recurse when seeing UnpackType + proper_item = self._parse_variadic_type(proper_item) + parsed_items.append(proper_item) + + if not parsed_items: + # empty concatenation, return UnpackType[Never] to indicate no variadic part + return UnpackType(UninhabitedType()) + + if len(parsed_items) == 1 and isinstance(unpack := parsed_items[0], UnpackType): + # single unpack, just return it directly + return unpack + + # more than one unpack: cast every member as Iterable[T] and unify the T's + item_types: list[Type] = [] + for item in parsed_items: + if isinstance(item, UnpackType): + # cast to Iterable[T] (or Any.from_error) + iterable_type = self.as_iterable_type(item.type) + item_type = ( + iterable_type.args[0] if isinstance(iterable_type, Instance) else iterable_type + ) + item_types.append(item_type) + else: + item_types.append(item) + unified_item_type = make_simplified_union(item_types) + return UnpackType(self.make_tuple_instance_type(unified_item_type)) + + def _materialize_variadic_union(self, unpacked: UnionType) -> UnpackType: + """Convert a Union of UnpackType into a single UnpackType.""" + # Currently, Union of star args are not part of the typing spec. + # Therefore, we need to reunify such unpackings. + # We create an upper bound by converting each union item to an iterable, + # and then returning the tuple unpacking *tuple[U₁ | U₂ | ... | Uₙ, ...] + # See Also: https://discuss.python.org/t/should-unions-of-tuples-tvts-be-allowed-inside-unpack/102608 + + # NOTE: We want to use set here, but we actually need stable ordering for unit tests. + parsed_items: list[UnpackType] = [] + seen_items: set[UnpackType] = set() + for proper_item in unpacked.proper_items: + # unions members should all be UnpackType themselves + assert isinstance(proper_item, UnpackType) + parsed_item = self._parse_variadic_type(proper_item) + if parsed_item not in seen_items: + parsed_items.append(parsed_item) + seen_items.add(parsed_item) + + if not parsed_items: + return UnpackType(UninhabitedType()) + + if len(parsed_items) == 1: + return parsed_items[0] + + # more than one unpack: cast every member as Iterable[T] and unify the T's + item_types: list[Type] = [] + for item in parsed_items: + # cast to Iterable[T] (or Any.from_error) + iterable_type = self.as_iterable_type(item.type) + item_type = ( + iterable_type.args[0] if isinstance(iterable_type, Instance) else iterable_type + ) + item_types.append(item_type) + unified_item_type = make_simplified_union(item_types) + return UnpackType(self.make_tuple_instance_type(unified_item_type)) + + def _unify_multiple_unpacks(self, items: list[Type]) -> list[Type]: + r"""If multiple UnpackType are present, unify them into a single Unpack[tuple[T, ...]].""" + # algorithm very similar to TupleNormalForm.from_items + seen_unpacks = 0 + prefix_items: list[Type] = [] + unpack_items: list[Type] = [] + suffix_items: list[Type] = [] + + for item in flatten_nested_tuples(items): + if isinstance(item, UnpackType): + seen_unpacks += 1 + unpack_items.extend(suffix_items) + unpack_items.append(item) + suffix_items.clear() + elif seen_unpacks: + suffix_items.append(item) + else: + prefix_items.append(item) + + if seen_unpacks <= 1: + # we can just use the original list + return items + + # unify all members of unpack_items into a single tuple[T, ...] + item_types = [] + for item in unpack_items: + if isinstance(item, UnpackType): + # cast to Iterable[T] (or Any.from_error) + iterable_type = self.as_iterable_type(item.type) + item_type = ( + iterable_type.args[0] if isinstance(iterable_type, Instance) else iterable_type + ) + item_types.append(item_type) + else: + item_types.append(item) + + unified_item_type = make_simplified_union(item_types) + unified_unpacked = UnpackType(self.make_tuple_instance_type(unified_item_type)) + return [*prefix_items, unified_unpacked, *suffix_items] def infer_function_type_arguments( diff --git a/mypy/messages.py b/mypy/messages.py index bbcc93ebfb25..35a1eb9ec92e 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -620,8 +620,8 @@ def untyped_function_call(self, callee: CallableType, context: Context) -> Type: def incompatible_argument( self, - n: int, - m: int, + n: int, # actual_argument_index (1-based) + m: int, # formal_parameter_index (1-based) callee: CallableType, arg_type: Type, arg_kind: ArgKind, @@ -997,7 +997,7 @@ def too_many_positional_arguments(self, callee: CallableType, context: Context) msg = "Too many positional arguments" else: msg = "Too many positional arguments" + for_function(callee) - self.fail(msg, context) + self.fail(msg, context, code=codes.CALL_ARG) self.maybe_note_about_special_args(callee, context) def maybe_note_about_special_args(self, callee: CallableType, context: Context) -> None: diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 350d57a7e4ad..20eeec30a6bb 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -67,7 +67,6 @@ UninhabitedType, UnionType, UnpackType, - find_unpack_in_list, flatten_nested_unions, get_proper_type, is_named_instance, @@ -668,6 +667,9 @@ def visit_param_spec(self, left: ParamSpecType) -> bool: def visit_type_var_tuple(self, left: TypeVarTupleType) -> bool: right = self.right + if isinstance(right, TupleType): + # simplify tuple[*Ts] -> Ts, etc. + right = right.simplify() if isinstance(right, TypeVarTupleType) and right.id == left.id: return left.min_len >= right.min_len return self._is_subtype(left.upper_bound, self.right) @@ -816,23 +818,46 @@ def visit_tuple_type(self, left: TupleType) -> bool: return False # At this point we know both fallbacks are non-tuple. return self._is_subtype(left.partial_fallback, right.partial_fallback) + elif isinstance(right, TypeVarTupleType): + # tuple[T1, ..., Tn] <: Ts if and only if tuple[T1, ..., Tn] <: tuple[*Ts] + return self._is_subtype(left, TupleType([UnpackType(right)], right.tuple_fallback)) else: return False def variadic_tuple_subtype(self, left: TupleType, right: TupleType) -> bool: """Check subtyping between two potentially variadic tuples. - Most non-trivial cases here are due to variadic unpacks like *tuple[X, ...], - we handle such unpacks as infinite unions Tuple[()] | Tuple[X] | Tuple[X, X] | ... + Most non-trivial cases here are due to variadic unpacks like *tuple[X, ...]. + Note that in certain cases, we should consider types as identical: + + - tuple[X, *tuple[X, ...]] and tuple[*tuple[X, ...], X] correspond to the same set of runtime values. + therefore, these types should be considered equivalent, even when proper_subtype is True. + + Note: + If proper_subtype is True, we treat tuple[X, ...] as the infinite union + Tuple[()] | Tuple[X] | Tuple[X, X] | ... + If proper_subtype is False, we treat tuple[X, ...] as an AnyOf type + AnyOf[Tuple[()], Tuple[X], Tuple[X, X], ...]. + See: https://github.com/python/typing/issues/566 + + Example: + tuple[X, ...] <: tuple[*tuple[X, ...], X] + then result is False if proper_subtype is True, otherwise True. + + X <: AnyOf[U1, U2, ...] iff X <: Ui for some i + AnyOf[T1, T2, ...] <: X iff X <: Ui for some i + X <: Union[U1, U2, ...] iff X <: Ui for some i + Union[T1, T2, ...] <: X iff X <: Ui for all i Note: the cases where right is fixed or has *Ts unpack should be handled by the caller. """ - right_unpack_index = find_unpack_in_list(right.items) + right_unpack_index = right.unpack_index + right_items = right.flattened_items if right_unpack_index is None: # This case should be handled by the caller. return False - right_unpack = right.items[right_unpack_index] + right_unpack = right.unpack assert isinstance(right_unpack, UnpackType) right_unpacked = get_proper_type(right_unpack.type) if not isinstance(right_unpacked, Instance): @@ -840,41 +865,43 @@ def variadic_tuple_subtype(self, left: TupleType, right: TupleType) -> bool: return False assert right_unpacked.type.fullname == "builtins.tuple" right_item = right_unpacked.args[0] - right_prefix = right_unpack_index - right_suffix = len(right.items) - right_prefix - 1 - left_unpack_index = find_unpack_in_list(left.items) + right_prefix = len(right.prefix) + right_suffix = len(right.suffix) + left_unpack_index = left.unpack_index + left_items = left.flattened_items + if left_unpack_index is None: # Simple case: left is fixed, simply find correct mapping to the right # (effectively selecting item with matching length from an infinite union). - if len(left.items) < right_prefix + right_suffix: + if len(left_items) < right_prefix + right_suffix: return False prefix, middle, suffix = split_with_prefix_and_suffix( - tuple(left.items), right_prefix, right_suffix + tuple(left_items), right_prefix, right_suffix ) if not all( - self._is_subtype(li, ri) for li, ri in zip(prefix, right.items[:right_prefix]) + self._is_subtype(li, ri) for li, ri in zip(prefix, right_items[:right_prefix]) ): return False if right_suffix and not all( - self._is_subtype(li, ri) for li, ri in zip(suffix, right.items[-right_suffix:]) + self._is_subtype(li, ri) for li, ri in zip(suffix, right_items[-right_suffix:]) ): return False return all(self._is_subtype(li, right_item) for li in middle) else: - if len(left.items) < len(right.items): + if self.proper_subtype and len(left_items) < len(right_items): # There are some items on the left that will never have a matching length # on the right. return False - left_prefix = left_unpack_index - left_suffix = len(left.items) - left_prefix - 1 - left_unpack = left.items[left_unpack_index] + left_prefix = len(left.prefix) + left_suffix = len(left.suffix) + left_unpack = left.unpack assert isinstance(left_unpack, UnpackType) left_unpacked = get_proper_type(left_unpack.type) if not isinstance(left_unpacked, Instance): # *Ts unpack can't be split, except if it is all mapped to Anys or objects. if self.is_top_type(right_item): right_prefix_types, middle, right_suffix_types = split_with_prefix_and_suffix( - tuple(right.items), left_prefix, left_suffix + tuple(right_items), left_prefix, left_suffix ) if not all( self.is_top_type(ri) or isinstance(ri, UnpackType) for ri in middle @@ -882,8 +909,8 @@ def variadic_tuple_subtype(self, left: TupleType, right: TupleType) -> bool: return False # Also check the tails match as well. return self._all_subtypes( - left.items[:left_prefix], right_prefix_types - ) and self._all_subtypes(left.items[-left_suffix:], right_suffix_types) + left_items[:left_prefix], right_prefix_types + ) and self._all_subtypes(left_items[-left_suffix:], right_suffix_types) return False assert left_unpacked.type.fullname == "builtins.tuple" left_item = left_unpacked.args[0] @@ -894,15 +921,24 @@ def variadic_tuple_subtype(self, left: TupleType, right: TupleType) -> bool: # and then check subtyping for all finite overlaps. if not self._is_subtype(left_item, right_item): return False + + # if proper_subtype is True, we test the Union case, otherwise the AnyOf case. + # For the former, *each* item on the left must be a subtype of *some* item on the right, + # for the latter, *some* item on the left must be a subtype of *some* item on the right. max_overlap = max(0, right_prefix - left_prefix, right_suffix - left_suffix) for overlap in range(max_overlap + 1): - repr_items = left.items[:left_prefix] + [left_item] * overlap + repr_items = left_items[:left_prefix] + [left_item] * overlap if left_suffix: - repr_items += left.items[-left_suffix:] + repr_items += left_items[-left_suffix:] left_repr = left.copy_modified(items=repr_items) - if not self._is_subtype(left_repr, right): + ok = self._is_subtype(left_repr, right) + # TODO: should we use more efficient "if ok XOR proper_subtype: return ok"? + if not ok and self.proper_subtype: return False - return True + elif ok and not self.proper_subtype: + return True + # TODO: should we just return proper_subtype directly? + return True if self.proper_subtype else False def is_top_type(self, typ: Type) -> bool: if not self.proper_subtype and isinstance(get_proper_type(typ), AnyType): diff --git a/mypy/test/testconstraints.py b/mypy/test/testconstraints.py index 277694a328c9..38f3717c8db8 100644 --- a/mypy/test/testconstraints.py +++ b/mypy/test/testconstraints.py @@ -3,7 +3,8 @@ from mypy.constraints import SUBTYPE_OF, SUPERTYPE_OF, Constraint, infer_constraints from mypy.test.helpers import Suite from mypy.test.typefixture import TypeFixture -from mypy.types import Instance, TupleType, UnpackType +from mypy.typeops import make_simplified_union +from mypy.types import Instance, TupleType, UnionType, UnpackType class ConstraintsSuite(Suite): @@ -22,63 +23,118 @@ def test_basic_type_variable(self) -> None: def test_basic_type_var_tuple_subtype(self) -> None: fx = self.fx + # Note: B <: A, so fallback for tuple[A, B] is tuple[A, ...] + fallback = Instance(fx.std_tuplei, [fx.a]) + target = TupleType([fx.a, fx.b], fallback=fallback) assert infer_constraints( Instance(fx.gvi, [UnpackType(fx.ts)]), Instance(fx.gvi, [fx.a, fx.b]), SUBTYPE_OF - ) == [ - Constraint(type_var=fx.ts, op=SUBTYPE_OF, target=TupleType([fx.a, fx.b], fx.std_tuple)) - ] + ) == [Constraint(type_var=fx.ts, op=SUBTYPE_OF, target=target)] def test_basic_type_var_tuple(self) -> None: - fx = self.fx - assert set( - infer_constraints( - Instance(fx.gvi, [UnpackType(fx.ts)]), Instance(fx.gvi, [fx.a, fx.b]), SUPERTYPE_OF - ) - ) == { - Constraint( - type_var=fx.ts, op=SUPERTYPE_OF, target=TupleType([fx.a, fx.b], fx.std_tuple) - ), - Constraint( - type_var=fx.ts, op=SUBTYPE_OF, target=TupleType([fx.a, fx.b], fx.std_tuple) - ), - } + # 1. create class A[T](NamedTuple): a: T; b: T; c: T + namedtuplei = self.fx.make_type_info("NamedTuple", module_name="typing") + ai = self.fx.make_type_info( + "A", typevars=[self.fx.t.name], mro=[namedtuplei, self.fx.std_tuplei, self.fx.oi] + ) + # 2. Create a class MyTuple[T](tuple[T, ...]) + bi = self.fx.make_type_info( + "MyTuple", typevars=[self.fx.t.name], mro=[self.fx.std_tuplei, self.fx.oi] + ) + + infer_constraints(Instance(ai, [self.fx.t]), Instance(bi, [self.fx.t]), SUBTYPE_OF) def test_type_var_tuple_with_prefix_and_suffix(self) -> None: fx = self.fx + # Note: B <: A and C <: A, so fallback for tuple[B, C] is tuple[B | C, ...] + fallback = Instance(fx.std_tuplei, [make_simplified_union([fx.b, fx.c])]) + target = TupleType([fx.b, fx.c], fallback=fallback) assert set( infer_constraints( + # GV[T, *TS, S] Instance(fx.gv2i, [fx.t, UnpackType(fx.ts), fx.s]), Instance(fx.gv2i, [fx.a, fx.b, fx.c, fx.d]), SUPERTYPE_OF, ) ) == { Constraint(type_var=fx.t, op=SUPERTYPE_OF, target=fx.a), - Constraint( - type_var=fx.ts, op=SUPERTYPE_OF, target=TupleType([fx.b, fx.c], fx.std_tuple) - ), - Constraint( - type_var=fx.ts, op=SUBTYPE_OF, target=TupleType([fx.b, fx.c], fx.std_tuple) - ), + Constraint(type_var=fx.ts, op=SUPERTYPE_OF, target=target), + Constraint(type_var=fx.ts, op=SUBTYPE_OF, target=target), Constraint(type_var=fx.s, op=SUPERTYPE_OF, target=fx.d), } + def test_wrapped_tuple_identical_results(self) -> None: + # test inferred constraints of tuple[T, ...] <: tuple[B, C] + # vs inferred constraints of tuple[*tuple[T, ...]] <: tuple[B, C] + fx = self.fx + t = Instance(fx.std_tuplei, [fx.t]) + + # check subtype constraints + assert ( + set( + infer_constraints( + t, TupleType([self.fx.b, self.fx.c], fallback=self.fx.std_tuple), SUBTYPE_OF + ) + ) + == set( + infer_constraints( + TupleType([UnpackType(t)], fallback=self.fx.std_tuple), + TupleType([self.fx.b, self.fx.c], fallback=self.fx.std_tuple), + SUBTYPE_OF, + ) + ) + == {Constraint(type_var=fx.t, op=SUBTYPE_OF, target=UnionType([fx.b, fx.c]))} + ) + + # check supertype constraints + assert ( + set( + infer_constraints( + t, TupleType([self.fx.b, self.fx.c], fallback=self.fx.std_tuple), SUPERTYPE_OF + ) + ) + == set( + infer_constraints( + TupleType([UnpackType(t)], fallback=self.fx.std_tuple), + TupleType([self.fx.b, self.fx.c], fallback=self.fx.std_tuple), + SUPERTYPE_OF, + ) + ) + == { + # TODO: replace with Intersection[B, C] once supported + Constraint(type_var=fx.t, op=SUPERTYPE_OF, target=fx.b), + Constraint(type_var=fx.t, op=SUPERTYPE_OF, target=fx.c), + } + ) + def test_unpack_homogeneous_tuple(self) -> None: fx = self.fx + # class GV[*Ts] + # template: GV[*tuple[T, ...]] + # actual: GV[A, B] + # So, T :> A and T :> B assert set( infer_constraints( Instance(fx.gvi, [UnpackType(Instance(fx.std_tuplei, [fx.t]))]), - Instance(fx.gvi, [fx.a, fx.b]), + Instance(fx.gvi, [fx.b, fx.c]), SUPERTYPE_OF, ) ) == { - Constraint(type_var=fx.t, op=SUPERTYPE_OF, target=fx.a), - Constraint(type_var=fx.t, op=SUBTYPE_OF, target=fx.a), + # TODO: replace with Intersection[B, C] once supported Constraint(type_var=fx.t, op=SUPERTYPE_OF, target=fx.b), - Constraint(type_var=fx.t, op=SUBTYPE_OF, target=fx.b), + Constraint(type_var=fx.t, op=SUPERTYPE_OF, target=fx.c), + # NOTE: TVTs are currently invariant, so we also get subtype constraints. + Constraint(type_var=fx.t, op=SUBTYPE_OF, target=UnionType([fx.b, fx.c])), } def test_unpack_homogeneous_tuple_with_prefix_and_suffix(self) -> None: fx = self.fx + # class GV2[T, *Ts, S] + # classes A, B, C, D with A :> B and A :> C + # template: GV2[T, *tuple[S, ...], U] + # actual: GV2[A, B, C, D]; + # prefix matching implies T :> A + # suffix matching implies U :> D + # unpack matching implies S :> B and S :> C and S <: B and S <: C assert set( infer_constraints( Instance(fx.gv2i, [fx.t, UnpackType(Instance(fx.std_tuplei, [fx.s])), fx.u]), @@ -87,11 +143,12 @@ def test_unpack_homogeneous_tuple_with_prefix_and_suffix(self) -> None: ) ) == { Constraint(type_var=fx.t, op=SUPERTYPE_OF, target=fx.a), + Constraint(type_var=fx.u, op=SUPERTYPE_OF, target=fx.d), + # TODO: replace with Intersection[B, C] once supported Constraint(type_var=fx.s, op=SUPERTYPE_OF, target=fx.b), - Constraint(type_var=fx.s, op=SUBTYPE_OF, target=fx.b), Constraint(type_var=fx.s, op=SUPERTYPE_OF, target=fx.c), - Constraint(type_var=fx.s, op=SUBTYPE_OF, target=fx.c), - Constraint(type_var=fx.u, op=SUPERTYPE_OF, target=fx.d), + # NOTE: TVTs are currently invariant, so we also get subtype constraints. + Constraint(type_var=fx.s, op=SUBTYPE_OF, target=UnionType([fx.b, fx.c])), } def test_unpack_with_prefix_and_suffix(self) -> None: diff --git a/mypy/tuple_normal_form.py b/mypy/tuple_normal_form.py new file mode 100644 index 000000000000..2f8a769318a1 --- /dev/null +++ b/mypy/tuple_normal_form.py @@ -0,0 +1,683 @@ +from __future__ import annotations + +from collections.abc import Iterable, Sequence +from itertools import chain +from typing import TYPE_CHECKING, Callable, NamedTuple, NewType, cast +from typing_extensions import TypeGuard, TypeIs + +from mypy.maptype import map_instance_to_supertype +from mypy.nodes import TypeInfo +from mypy.typeops import make_simplified_union +from mypy.types import ( + AnyType, + Instance, + ParamSpecType, + TupleType, + Type, + TypeList, + TypeOfAny, + TypeVarTupleType, + UninhabitedType, + UnionType, + UnpackType, + flatten_nested_tuples, + get_proper_type, +) + +if TYPE_CHECKING: + from mypy.infer import ArgumentInferContext, TupleInstanceType + +DirtyUnpackType = NewType("DirtyUnpackType", UnpackType) +r"""An UnpackType that may contain unexpected members, such as TypeList or UnionType.""" + + +def is_variadic_tuple(typ: Type, /) -> bool: + p_t = get_proper_type(typ) + return isinstance(p_t, TupleType) and p_t.is_variadic + + +def get_std_tuple_typeinfo(typ: TupleType, /) -> TypeInfo: + """Extract the TypeInfo of 'builtins.tuple' from a TupleType.""" + fallback = typ.partial_fallback + if fallback.type.fullname == "builtins.tuple": + return fallback.type + + # this can happen for instance for named tuples + for base in fallback.type.mro: + if base.fullname == "builtins.tuple": + return base + raise RuntimeError("Could not find builtins.tuple in the MRO of the fallback type") + + +class TupleHelper: + """Helper class for certain tuple operations.""" + + tuple_typeinfo: TypeInfo + + def __init__(self, tuple_type: TypeInfo | TupleType | Instance, /) -> None: + if isinstance(tuple_type, Instance): + tuple_type = tuple_type.type + + if isinstance(tuple_type, TupleType): + tuple_type = get_std_tuple_typeinfo(tuple_type) + + if tuple_type.fullname != "builtins.tuple": + raise ValueError(f"Expected 'builtins.tuple' TypeInfo, got {tuple_type}") + self.tuple_typeinfo = tuple_type + + @property + def std_tuple(self) -> Instance: + """return tuple[Any, ...]""" + return Instance(self.tuple_typeinfo, [AnyType(TypeOfAny.from_omitted_generics)]) + + def is_tuple_instance_type(self, typ: Type, /) -> TypeIs[TupleInstanceType]: + """Check if the type is a tuple instance, i.e. tuple[T, ...].""" + p_t = get_proper_type(typ) + return isinstance(p_t, Instance) and p_t.type == self.tuple_typeinfo + + def is_tuple_instance_subtype(self, typ: Type, /) -> TypeGuard[Instance]: + """Check if the type is a subtype of tuple[T, ...] for some T.""" + from mypy.subtypes import is_subtype + + p_t = get_proper_type(typ) + + if not isinstance(p_t, Instance): + return False + if p_t.type == self.tuple_typeinfo: + return True + # otherwise, check if it is a subtype of tuple[Any, ...] + return is_subtype(typ, self.std_tuple) + + def as_tuple_instance_type(self, typ: Type, /) -> TupleInstanceType: + r"""Upcast a subtype of tuple[T, ...] to tuple[T, ...].""" + if not self.is_tuple_instance_subtype(typ): + raise ValueError(f"Type {typ} is not a subtype of tuple[T, ...]") + + # TODO: does this always give the same result as the solver? + tuple_instance = map_instance_to_supertype(typ, self.tuple_typeinfo) + return cast("TupleInstanceType", tuple_instance) + + def make_tuple_instance_type(self, arg: Type, /) -> TupleInstanceType: + """Create a TupleInstance type with the given argument type.""" + value = Instance(self.tuple_typeinfo, [arg]) + return cast("TupleInstanceType", value) + + def make_tuple_type(self, items: Sequence[Type], /) -> TupleType: + r"""Create a proper TupleType from the given item types.""" + self._validate_items_for_tuple_type(items) + # make the fallback type + fallback = self._make_fallback_for_tuple_items(items) + return TupleType(items, fallback=fallback) + + def get_variadic_fallback(self, unpack: UnpackType, /) -> TupleInstanceType: + """Get a tuple fallback for the content of an UnpackType.""" + unpacked = get_proper_type(unpack.type) + + if self.is_tuple_instance_type(unpacked): + return unpacked + + elif isinstance(unpacked, TypeVarTupleType): + tuple_fallback = unpacked.tuple_fallback.copy_modified( + args=[AnyType(TypeOfAny.implementation_artifact)] + ) + assert self.is_tuple_instance_type(tuple_fallback) + return tuple_fallback + + elif isinstance(unpacked, ParamSpecType): + upper_bound = get_proper_type(unpacked.upper_bound) + assert self.is_tuple_instance_type(upper_bound) + tuple_fallback = upper_bound.copy_modified( + args=[AnyType(TypeOfAny.implementation_artifact)] + ) + assert self.is_tuple_instance_type(tuple_fallback) + return tuple_fallback + + elif isinstance(unpacked, TupleType): + raise TypeError(f"Expected unpack to be a pure variadic type, got {unpacked}") + + else: + raise TypeError(f"Got unexpected unpack type {unpacked}") + + def _make_fallback_for_tuple_items(self, items: Sequence[Type], /) -> Instance: + item_types = [] + for item in flatten_nested_tuples(items): + if isinstance(item, UnpackType): + unpacked = get_proper_type(item.type) + if self.is_tuple_instance_type(unpacked): + item_types.append(unpacked.args[0]) + elif isinstance(unpacked, TypeVarTupleType): + item_types.append(AnyType(TypeOfAny.from_omitted_generics)) + elif isinstance(unpacked, ParamSpecType): + item_types.append(AnyType(TypeOfAny.from_omitted_generics)) + else: + assert False, f"Unexpected unpacked type: {unpacked}" + else: + item_types.append(item) + + combined_item_type = make_simplified_union(item_types) + return self.make_tuple_instance_type(combined_item_type) + + def _validate_items_for_tuple_type(self, items: Sequence[Type], /) -> None: + """Validate that the items are valid for a TupleType.""" + seen_unpack = 0 + for item in flatten_nested_tuples(items): + if isinstance(item, UnpackType): + seen_unpack += 1 + unpacked = get_proper_type(item.type) + if not ( + self.is_tuple_instance_type(unpacked) + or isinstance(unpacked, (TypeVarTupleType, ParamSpecType)) + ): + raise ValueError( + f"UnpackType must contain tuple[T, ...] or TypeVarTuple, got {unpacked}" + ) + if seen_unpack > 1: + raise ValueError("TupleType can only have one UnpackType") + + def _get_variadic_item_type(self, tup: TupleType, /) -> Type | None: + """Get the type of the variadic part of a tuple, or None if there is no variadic part.""" + unpack_index = tup.unpack_index + if unpack_index is None: + return None + + item = tup.flattened_items[unpack_index] + assert isinstance(item, UnpackType) + + return self._get_variadic_item_type_from_unpack(item) + + def _get_variadic_item_type_from_unpack(self, unpack: UnpackType, /) -> Type: + """Get the type of the variadic part from an UnpackType.""" + unpacked = get_proper_type(unpack.type) + if self.is_tuple_instance_type(unpacked): + # unpacked is tuple[T, ...], return T + return unpacked.args[0] + elif isinstance(unpacked, TypeVarTupleType): + # unpacked is a TypeVarTuple, return Any + return AnyType(TypeOfAny.from_omitted_generics) + elif isinstance(unpacked, ParamSpecType): + # need for some specific cases like + return unpack + else: + assert False, f"Unexpected unpacked type: {unpacked}" + + def get_item(self, tup: TupleType, /, index: int) -> Type | None: + r"""Get the item at the given index, treating the variadic part as arbitrarily long. + + Returns: + None: If the index is out of bounds and the tuple has no variadic part. + Type: If the index is in bounds or the tuple has no variadic part. + + Otherwise, pretend the variadic part has arbitrarily many items of the appropriate type. + + tuple[P1, ..., Pn, *Vs, S1, ..., Sm] @ index = + Iterable_type[Vs] if index < -m + S[index] if -m ≤ index < 0 + P[index] if 0 ≤ index < n + Iterable_type[Vs] if index ≥ n + """ + flattened_items = tup.flattened_items + unpack_index = tup.unpack_index + + if unpack_index is None: + try: + return flattened_items[index] + except IndexError: + return None + + N = len(flattened_items) + if unpack_index - N < index < unpack_index: + return flattened_items[index] + + item = flattened_items[unpack_index] + assert isinstance(item, UnpackType) + return self._get_variadic_item_type_from_unpack(item) + + def get_slice( + self, tup: TupleType, /, start: int | None, stop: int | None, step: int = 1 + ) -> TupleType: + r"""Get a slice of the tuple, treating the variadic part as arbitrarily long. + + Returns: + TupleType: The sliced tuple type. + + If the tuple has no variadic part, this works like regular slicing. + If the tuple has a variadic part, the shape of the slice depends on the signs + of start and stop, as described below: + + 1. If both start and stop are same signed integers (both non-negative or both negative), + then we slice as if the variadic part was expanded into an infinite sequence + of items (whose type we get by casting the unpack type to Iterable[T] and taking T) + 2. In all other cases, this works like regular slicing, treating the variadic part as a single item. + However, only step=1 and step=-1 are supported in this case. + + t = tuple[P1, ..., Pn, *Vs, S1, ..., Sm] + + Depending on the sign of start, the starting point is determined as follows: + If start ≥ 0, then start = min(start, n) + If start < 0, then start = max(start, -m) + which corresponds to taking items from the prefix if start is non-negative, + and from the suffix if start is negative, but never going beyond the variadic part. + + slices that 'traverse' the variadic part always include the entire variadic part, + irrespective of the step size. + + The slice is constructed as follows: + - if both start and stop are within the prefix or both within the suffix, + just do regular slicing + - If they traverse the variadic part, create two slices and glue them with the variadic part in between. + """ + # NOTE: This works differently from TupleType.slice! + flattened_items = tup.flattened_items + unpack_index = tup.unpack_index + + if unpack_index is None: + return self.make_tuple_type(flattened_items[start:stop:step]) + + variadic_part = flattened_items[unpack_index] + assert isinstance(variadic_part, UnpackType) + iterable_type = self._get_variadic_item_type_from_unpack(variadic_part) + + prefix_length = len(tup.prefix) + suffix_length = len(tup.suffix) + + # clip start and stop to the valid range [-suffix_length, +prefix_length] + clip: Callable[[int], int] = lambda x: max(min(-suffix_length, -1), min(x, prefix_length)) + start = None if start is None else clip(start) + stop = None if stop is None else clip(stop) + assert step != 0, "slice step cannot be zero" + + # a slice within the prefix + if (start is None or start >= 0) and (stop is not None and stop >= 0): + start = 0 if start is None else start + items = [ + flattened_items[i] if i < prefix_length else iterable_type + for i in range(start, stop, step) + ] + # a slice within the suffix + elif (start is not None and start < 0) and (stop is None or stop < 0): + stop = 0 if stop is None else stop + items = [ + flattened_items[i] if i >= -suffix_length else iterable_type + for i in range(start, stop, step) + ] + # a slice that traverses the variadic part in the forward direction + elif (start is None or start >= 0) and (stop is None or stop < 0) and step > 0: + assert step == 1, "Only step=+1 supported when slicing forward across variadic part" + items = [ + *flattened_items[start:unpack_index:step], + variadic_part, + *flattened_items[unpack_index + 1 : stop : step], + ] + # a slice that traverses the variadic part in the backward direction + elif (start is None or start < 0) and (stop is None or stop >= 0) and step < 0: + assert step == -1, "Only step=-1 supported when slicing backward across variadic part" + items = [ + *flattened_items[start : unpack_index + 1 : step], + variadic_part, + *flattened_items[unpack_index - 1 : stop : step], + ] + # empty slice + else: + items = [] + + return self.make_tuple_type(items) + + +def _is_empty_unpack(typ: Type, /) -> TypeGuard[UnpackType]: + """Check if the variadic part is empty.""" + proper_arg = get_proper_type(typ) + if not isinstance(proper_arg, UnpackType): + return False + + content = get_proper_type(proper_arg.type) + if isinstance(content, UninhabitedType): + return True + elif isinstance(content, (TypeList, UnionType, TupleType)): + return all(_is_empty_unpack(item) for item in content.items) + # fallback: TypeVarTupleType, tuple[T, ...], list[T], etc. + # TODO: should we try converting to Iterable[T] and check if T is UninhabitedType? + return False + + +def _is_non_empty_unpack(typ: Type, /) -> TypeGuard[UnpackType]: + """Check if the variadic part is non-empty.""" + proper_arg = get_proper_type(typ) + if not isinstance(proper_arg, UnpackType): + return False + + content = get_proper_type(proper_arg.type) + if isinstance(content, UninhabitedType): + return False + elif isinstance(content, (TypeList, UnionType, TupleType)): + return any(_is_non_empty_unpack(item) for item in content.items) + # fallback: TypeVarTupleType, tuple[T, ...], list[T], etc. + # TODO: should we try converting to Iterable[T] and check if T is UninhabitedType? + return True + + +class TupleNormalForm(NamedTuple): + r"""For a given tuple type `t`, it's Normal Form is defined as the representation: + + t = tuple[P1, ..., Pn, *Vs?, S1, ..., Sm] + + where: + + - P1, ..., Pn is the maximal statically known finite prefix of the tuple, + - Vs is the (potentially missing) variable part of the tuple, + - S1, ..., Sm is the maximal statically known finite suffix of the tuple + - If the tuple has no variable part, both Vs and S1, ..., Sm are empty. + + Note: + Special attention must be paid when constructing a tuple from a TupleNormalForm, + since the variadic part contains unexpected `UnpackType` members, + specifically `TypeList`, `UnionType`, `UninhabitedType` and potentially `ParamSpecType`. + + - `UnpackType[TypeList[T1, ..., Tn]]` should be interpreted as unpacking + a concatenation of multiple variadic parts, e.g. `f(*[T1, ..., Tn])` + - `UnpackType[UnionType[T1, ..., Tn]]` should be interpreted as unpacking + a union of multiple variadic items `x: T1 | T2 | ... | Tn; f(*x)` + - Note that ``*Union[A, B] is equivalent to ``Union[*A, *B]`` + - Note that ``*TypeList[*TypeList[A1, ..., An], *TypeList[B1, ... Bm]]`` + is equivalent to ``*TypeList[A1, ..., An, B1, ..., Bm]`` + + Attributes: + - prefix: the longest statically known finite prefix of the tuple + - variadic: an improper `UnpackType` representing the variable part of the tuple + - This can contain things that are usually not allowed in `UnpackType`, in particular: + - `TypeList` representing concatenation of multiple variadic parts + - `UnionType` representing a union of multiple variadic parts + - `ParamSpecType` representing `*P.args` + - suffix: the longest statically known finite suffix of the remaining tuple + """ + + prefix: Sequence[Type] + variadic: DirtyUnpackType + suffix: Sequence[Type] + + @property + def is_variadic(self) -> bool: + """Inspect if the tuple has a variable part.""" + return not _is_empty_unpack(self.variadic) + + @property + def minimum_length(self) -> int: + """The minimum length of the tuple represented by this TupleNormalForm. + + If the tuple is not variadic, this coincides with the actual length. + """ + # NOTE: Technically the variadic part could produce additional items, + # if multiple unpacks are present, e.g. + # tuple[int, *tuple[int, ...], str, *tuple[str, ...], str] + # is at least length 3. + # However we treat this as if it were tuple[int, *tuple[T, ...], str] + return len(self.prefix) + len(self.suffix) + + def materialize(self, context: ArgumentInferContext) -> TupleType: + """Construct the actual TupleType from the TupleNormalForm. + + Since this method needs access to the `TypeInfo` of `builtins.tuple` + and `typing.Iterable`, we require the caller to provide an `ArgumentInferContext`. + """ + return context.materialize_tnf(self) + + @staticmethod + def from_star_parameter(star_param: Type, /) -> TupleNormalForm: + """Create a TupleNormalForm from the type of a ``*args: T`` annotation. + + During Semantic Analysis, the type of `*args: T` is not always wrapped in `UnpackType`. + in particular, ``*args: int`` just gives `int`. + + See Also: `from_star_arg` for types passed as star arguments. + """ + p_t = get_proper_type(star_param) + if isinstance(p_t, UnpackType): + # we can use the same logic as from_star_argument + return TupleNormalForm.from_star_argument(p_t) + elif isinstance(p_t, ParamSpecType): + # ParamSpecType is always variadic + variadic_part = UnpackType(p_t, from_star_syntax=True) + return TupleNormalForm([], DirtyUnpackType(variadic_part), []) + else: + # otherwise we have an annotation like `*args: int` + # this should be treated as if it were `*args: *tuple[int, ...]` + # we deal with this by representing it as Unpack[] + # despite being conceptually equal to a single item, during materialization + # this will be converted back to tuple[int, ...] in + variadic_part = UnpackType(TypeList([p_t]), from_star_syntax=True) + return TupleNormalForm([], DirtyUnpackType(variadic_part), []) + + @staticmethod + def from_star_argument(star_arg: Type, /) -> TupleNormalForm: + """Create a TupleNormalForm from a type that was passed as a star argument. + + Uses special cases for tuple types and unions of tuples. + Note that during typ analysis, the types are not wrapped in `UnpackType`, + so we should not see `UnpackType` here. + + On the flipside, when we see *any* variadic type, including + `TypeVarTupleType`, `ParamSpec.args`, `list[T]`, etc., then we wrap it in + an `UnpackType` when adding it to the variadic part of the TupleNormalForm. + + Examples: + - list[int] -> [], [list[int]], [] + - tuple[int, str] -> [int, str], [], [] + - tuple[*tuple[str, ...], str] -> [], [*tuple[str, ...]], [str] + - Ts -> [], [*Ts], [] + - P.args -> [], [P], [] + - list[Never] -> [], [list[Never]], [] + + Some special casing is applied to unions: + - list[int] | list[str] -> [], [list[int] | list[str]], [] + - tuple[int, str] | list[str] -> [], [tuple[int, str] | list[str]], [] + + """ + p_t = get_proper_type(star_arg) + if isinstance(p_t, UnpackType): + # Note: mypy is inconsistent regarding wrapping types in UnpackType. + # def foo(*args: *tuple[int, ...]): ...; + # def outer(*args: *tuple[int, ...]): + # foo(*x) # x --> Instance(tuple), not UnpackType + # def bar(*args: *Ts): ...; + # def outer(*args: *Ts): ... + # bar(*args) # args --> UnpackType(TypeVarTupleType) + p_t = get_proper_type(p_t.type) + + assert not isinstance(p_t, UnpackType), f"Unexpected UnpackType: {star_arg}" + + # special case single tuple + if isinstance(p_t, TupleType): + return TupleNormalForm.from_items(p_t.items) + + # special case union of tuples + elif isinstance(p_t, UnionType): + # if all items are tuples, we can split them + tnfs = [TupleNormalForm.from_star_argument(x) for x in p_t.proper_items] + return TupleNormalForm.combine_union(tnfs) + + # assume that the star args is some variadic type, + # e.g. ParamSpec, TypeVarTupleType, tuple[T, ...], list[T], etc. + # wrap it in UnpackType[TypeList]. + else: + variadic_part = UnpackType(star_arg, from_star_syntax=True) + return TupleNormalForm([], DirtyUnpackType(variadic_part), []) + + @staticmethod + def from_items(items: Iterable[Type], /) -> TupleNormalForm: + r"""Split a tuple (or list of items) into 3 parts: head part, body part and tail part. + + 1. A head part which is the longest finite prefix of the tuple + 2. A body part which covers all items from the first variable item to the last variable item + 3. A tail part which is the longest finite suffix of the remaining tuple + + If the body part is empty, the tail part is empty as well. + The body part, if non-empty, always starts and ends with a variable item (UnpackType). + Note that according to the current specification, the body part may contain at maximum + a single variable item (UnpackType), so the body part actually should at maximum be + of length 1. This implementation should still work if that specification changes in the future. + + Examples: + - tuple[int, str] -> ([int, str], [], []) + - tuple[int, *tuple[int, ...]] -> ([int], [*tuple[int, ...]], []) + - tuple[*tuple[int, ...], int] -> ([], [*tuple[int, ...]], [int]) + - tuple[int, *tuple[int, ...], int] -> ([int], [*tuple[int, ...]], [int]) + - tuple[int, *tuple[int, ...], str, *tuple[str, ...], int] + -> ([int], [*tuple[int, ...], str, *tuple[str, ...]], [int]) + """ + head_items: list[Type] = [] + tail_items: list[Type] = [] + body_items: list[Type] = [] + seen_variadic = False + + # determine the head, body and tail parts + for item in flatten_nested_tuples(items): + if _is_empty_unpack(item): + # skip empty unpacks + continue + elif _is_non_empty_unpack(item): + seen_variadic = True + body_items.extend(tail_items) + body_items.append(item) + tail_items.clear() + elif seen_variadic: + tail_items.append(item) + else: + head_items.append(item) + + # the variadic part is the unpacking of the concatenation of all body items + # formally represented by a UnpackType[TypeList[...]] + body = UnpackType(TypeList(body_items), from_star_syntax=True) + return TupleNormalForm(head_items, DirtyUnpackType(body), tail_items) + + @staticmethod + def combine_union(args: Sequence[TupleNormalForm], /) -> TupleNormalForm: + """Combine a union of TupleNormalForm into a single TupleNormalForm. + + - The head will be the element-wise union of all heads, stopping when one of the heads is exhausted. + - the body will be a special UnionType[TypeList[...]] construct + - The tail will be the element-wise union of all tails, stopping when one of the tails is exhausted. + Note that for body-less union members, any head items that were not consumed when creating the + joint head are prepended to the tail. + + In particular, if any single one of the inputs is head-less, then the resulting head is also empty. + + Examples: + tuple[int, int], tuple[None, None] + --> [int | None], *TypeList[], [int | None] + + tuple[int, *tuple[int, ...], int], + tuple[None, *tuple[None, ...], None] + --> [int | None], + *Union[ + TypeList[*tuple[int, ...]], + TypeList[*tuple[None, ...]] + ], + Unpack[*tuple[int, ...] | *tuple[None, ...]] + [int | None] + + tuple[int, *tuple[int, ...], str, *tuple[str, ...], int] + tuple[None, *tuple[None, ...], None] + --> [int | None], + *Union[ + TypeList[*tuple[int, ...], str, *tuple[str, ...]], + TypeList[*tuple[None, ...]], + ], + [int | None] + + tuple[int, str], list[None] + --> [], + *Union[ + TypeList[int, str], + TypeList[*list[None]] + ], + [] + + tuple[int, int] | tuple[*tuple[int, ...], int] + --> [], [[[int], [*tuple[int, ...]]], [int] + """ + # split each tuple + heads: tuple[list[Type], ...] + bodies: tuple[UnpackType, ...] + tails: tuple[list[Type], ...] + heads, bodies, tails = zip(*args) + + # setup + remaining_head_items: list[list[Type]] + remaining_tail_items: list[list[Type]] + remaining_body_items: list[list[Type]] + target_head_items: list[Type] = [] + target_tail_items: list[Type] = [] + + # 1. process all heads in parallel, stopping when one of the heads is exhausted + shared_head_length = min(len(head) for head in heads) + for items in zip(*(head[:shared_head_length] for head in heads)): + # append the union of the items to the head part + target_head_items.append(make_simplified_union(items)) + # collect all the remaining head items from generators that were not exhausted + remaining_head_items = [head[shared_head_length:] for head in heads] + + # If a tuple has no body items, prepend the remaining head items to the tail. + # This addresses cases like combining `tuple[A, B, C]` with `tuple[X, *tuple[Y, ...], Z]`. + # which should yield tuple[A | X, *tuple[B | Y, ...], C | Z] + for remaining_head, body, tail in zip(remaining_head_items, bodies, tails): + if _is_empty_unpack(body): + # move all remaining head items to the start of the tail + _tail = tail[:] + tail.clear() + tail.extend(remaining_head) + tail.extend(_tail) + remaining_head.clear() + + # 2. process all tails in parallel, in reverse, stopping when one of the tails is exhausted + shared_tail_length = min(len(tail) for tail in tails) + for items in zip(*(tail[-1 : -shared_tail_length - 1 : -1] for tail in tails)): + # append the union of the items to the tail part + target_tail_items.append(make_simplified_union(items)) + # collect all the remaining tail items from generators that were not exhausted + target_tail_items.reverse() # reverse to maintain original order + remaining_tail_items = [tail[: len(tail) - shared_tail_length] for tail in tails] + # note: do not use tail[:-shared_tail_length]; breaks when shared_tail_length=0 + + # 3. process all bodies + assert len(remaining_head_items) == len(remaining_tail_items) == len(bodies) + remaining_body_items = [ + [ + *remaining_head, + body, + *remaining_tail, + ] # TODO: expand body in case like Unpack[] ? + for remaining_head, body, remaining_tail in zip( + remaining_head_items, bodies, remaining_tail_items + ) + ] + + # 4. collected all items that will be put into the variable part + # Note: if the collection is empty, this will give UninhabitedType. + joined_bodies = UnionType.make_union( + [UnpackType(TypeList(body_items)) for body_items in remaining_body_items] + ) + variadic_part = UnpackType(joined_bodies, from_star_syntax=True) + + # 5. combine all parts into a TupleNormalForm + return TupleNormalForm( + target_head_items, DirtyUnpackType(variadic_part), target_tail_items + ) + + @staticmethod + def combine_concat(tnfs: Sequence[TupleNormalForm], /) -> TupleNormalForm: + """Combine sequence of TupleNormalForm into a single TupleNormalForm. + + essentially converts ``(*x1, ..., *xn)`` -> ``*x` where x = [*x1, ..., *xn]`` + """ + if len(tnfs) == 0: + return TupleNormalForm([], DirtyUnpackType(UnpackType(UninhabitedType())), []) + + if len(tnfs) == 1: + return tnfs[0] + + items = ( + item + for item in chain.from_iterable( + (*tnf.prefix, tnf.variadic, *tnf.suffix) for tnf in tnfs + ) + ) + return TupleNormalForm.from_items(items) diff --git a/mypy/typeops.py b/mypy/typeops.py index 02e1dbda514a..2551995b5308 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -102,13 +102,15 @@ def tuple_fallback(typ: TupleType) -> Instance: info = typ.partial_fallback.type if info.fullname != "builtins.tuple": return typ.partial_fallback - items = [] + items: list[Type] = [] for item in typ.items: if isinstance(item, UnpackType): unpacked_type = get_proper_type(item.type) if isinstance(unpacked_type, TypeVarTupleType): - unpacked_type = get_proper_type(unpacked_type.upper_bound) - if ( + # TODO: In principle the correct value is Union[*Ts] + # until this is supported use Any. + items.append(AnyType(TypeOfAny.special_form)) + elif ( isinstance(unpacked_type, Instance) and unpacked_type.type.fullname == "builtins.tuple" ): diff --git a/mypy/types.py b/mypy/types.py index 207e87984bed..a6bfd255fab4 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -61,6 +61,9 @@ from mypy.state import state from mypy.util import IdMapper +if TYPE_CHECKING: + from mypy.infer import TupleInstanceType + T = TypeVar("T") JsonDict: _TypeAlias = dict[str, Any] @@ -1206,9 +1209,9 @@ class TypeList(ProperType): items: list[Type] - def __init__(self, items: list[Type], line: int = -1, column: int = -1) -> None: + def __init__(self, items: Sequence[Type], line: int = -1, column: int = -1) -> None: super().__init__(line, column) - self.items = items + self.items = list(items) def accept(self, visitor: TypeVisitor[T]) -> T: assert isinstance(visitor, SyntheticTypeVisitor) @@ -1230,7 +1233,8 @@ class UnpackType(ProperType): or unpacking * syntax. The inner type should be either a TypeVarTuple, or a variable length tuple. - In an exceptional case of callable star argument it can be a fixed length tuple. + In an exceptional case of callable star argument it can be a fixed length tuple or + ParamSpecType. Note: the above restrictions are only guaranteed by normalizations after semantic analysis, if your code needs to handle UnpackType *during* semantic analysis, it is @@ -2796,7 +2800,7 @@ class TupleType(ProperType): def __init__( self, - items: list[Type], + items: Sequence[Type], fallback: Instance, line: int = -1, column: int = -1, @@ -2804,7 +2808,7 @@ def __init__( ) -> None: super().__init__(line, column) self.partial_fallback = fallback - self.items = items + self.items = list(items) self.implicit = implicit def can_be_true_default(self) -> bool: @@ -2950,6 +2954,95 @@ def slice( slice_items = self.items[begin:end:stride] return TupleType(slice_items, fallback, self.line, self.column, self.implicit) + # TODO: Consider caching these properties. + @property + def flattened_items(self) -> list[Type]: + r"""Return a list of types for the items in this tuple (flattened). + + Does not expand TypeAliases, except when they are appearing as tuples inside Unpack. + """ + return flatten_nested_tuples(self.items) + + @property + def unpack_index(self) -> int | None: + r"""The index of the Unpack in the tuple, or None if there is none.""" + return find_unpack_in_list(self.flattened_items) + + @property + def prefix(self) -> list[Type]: + r"""The prefix are all items before the first unpack.""" + return self.flattened_items[: self.unpack_index] + + @property + def suffix(self) -> list[Type]: + r"""The suffix are all items after the last unpack (or none, if no unpack exists).""" + unpack_index = self.unpack_index + if unpack_index is None: + return [] + return self.flattened_items[unpack_index + 1 :] + + @property + def unpack(self) -> UnpackType | None: + r"""The Unpack in the tuple, or None if there is none.""" + unpack_index = self.unpack_index + if unpack_index is None: + return None + unpack = self.flattened_items[unpack_index] + assert isinstance(unpack, UnpackType) + return unpack + + @property + def is_variadic(self) -> bool: + r"""The variadic item if the tuple has one Unpack, otherwise None.""" + return self.unpack_index is not None + + @property + def minimum_length(self) -> int: + r"""The minimum length of the tuple. + + Note: + We assume that the variadic part has at least 0 items. + This assumption depends on the typing specification stating that + a tuple can have at most one unpack. If this is relaxed, then this + property may need to be changed. For example, currently + `tuple[int, *tuple[str, ...], int, *tuple[str, ...], int]` is illegal. + This tuple would have prefix `[int]` and suffix `[int]`, + but its minimum length is 3, not 2. + """ + return len(self.prefix) + len(self.suffix) + + def simplify(self) -> TupleType | TupleInstanceType | ParamSpecType: + r"""Simplify a tuple type. + + 1. expand nested unpacks + 2. if the tuple is a single unpack, return the unpacked type + + Example: + tuple[*tuple[int, ...]] -> tuple[int, ...] + tuple[*tuple[int, str]] -> tuple[int, str] + tuple[*ParamSpec] -> ParamSpec + """ + flattened_items = self.flattened_items + if len(flattened_items) == 1: + first_item = get_proper_type(flattened_items[0]) + if isinstance(first_item, UnpackType): + proper_unpacked = get_proper_type(first_item.type) + if isinstance(proper_unpacked, TupleType): + return proper_unpacked.simplify() + elif isinstance(proper_unpacked, ParamSpecType): + return proper_unpacked + elif ( + isinstance(proper_unpacked, Instance) + and proper_unpacked.type.fullname == "builtins.tuple" + ): + return cast("TupleInstanceType", proper_unpacked) + elif isinstance(proper_unpacked, TypeVarTupleType): + # do not unpack TypeVarTupleType + return self + else: + assert False, f"unexpected unpacked type {proper_unpacked!r}" + return self + class TypedDictType(ProperType): """Type of TypedDict object {'k1': v1, ..., 'kn': vn}. @@ -3449,6 +3542,19 @@ def read(cls, data: ReadBuffer) -> UnionType: assert read_tag(data) == END_TAG return ret + @property + def proper_items(self) -> list[ProperType]: + """Return a list of proper types for the items in this union (flattened).""" + # similar to flatten_nested_unions, but expands type aliases + res = [] + for typ in self.items: + p_t = get_proper_type(typ) + if isinstance(p_t, UnionType): + res.extend(p_t.proper_items) + else: + res.append(p_t) + return res + class PartialType(ProperType): """Type such as List[?] where type arguments are unknown, or partial None type. diff --git a/mypyc/test-data/irbuild-lists.test b/mypyc/test-data/irbuild-lists.test index d864bfd19df2..e05ddb7a2cc2 100644 --- a/mypyc/test-data/irbuild-lists.test +++ b/mypyc/test-data/irbuild-lists.test @@ -929,9 +929,10 @@ def test(): r19 :: list r20, r21 :: native_int r22 :: bit - r23, x :: object - r24 :: str - r25 :: native_int + r23 :: object + r24, x :: union[tuple[str, str], str, int] + r25 :: str + r26 :: native_int a :: list L0: r0 = 'abc' @@ -962,12 +963,13 @@ L1: if r22 goto L2 else goto L4 :: bool L2: r23 = list_get_item_unsafe r10, r20 - x = r23 - r24 = PyObject_Str(x) - CPyList_SetItemUnsafe(r19, r20, r24) + r24 = cast(union[tuple[str, str], str, int], r23) + x = r24 + r25 = PyObject_Str(x) + CPyList_SetItemUnsafe(r19, r20, r25) L3: - r25 = r20 + 1 - r20 = r25 + r26 = r20 + 1 + r20 = r26 goto L1 L4: a = r19 diff --git a/mypyc/test-data/irbuild-tuple.test b/mypyc/test-data/irbuild-tuple.test index 0ae1a2f0cda6..243a2aaf54f2 100644 --- a/mypyc/test-data/irbuild-tuple.test +++ b/mypyc/test-data/irbuild-tuple.test @@ -922,9 +922,10 @@ def test(): r19 :: tuple r20, r21 :: native_int r22 :: bit - r23, x :: object - r24 :: str - r25 :: native_int + r23 :: object + r24, x :: union[tuple[str, str], str, int] + r25 :: str + r26 :: native_int a :: tuple L0: r0 = 'abc' @@ -955,12 +956,13 @@ L1: if r22 goto L2 else goto L4 :: bool L2: r23 = list_get_item_unsafe r10, r20 - x = r23 - r24 = PyObject_Str(x) - CPySequenceTuple_SetItemUnsafe(r19, r20, r24) + r24 = cast(union[tuple[str, str], str, int], r23) + x = r24 + r25 = PyObject_Str(x) + CPySequenceTuple_SetItemUnsafe(r19, r20, r25) L3: - r25 = r20 + 1 - r20 = r25 + r26 = r20 + 1 + r20 = r26 goto L1 L4: a = r19 diff --git a/test-data/unit/check-abstract.test b/test-data/unit/check-abstract.test index 7507a31d115a..5a43afb268fa 100644 --- a/test-data/unit/check-abstract.test +++ b/test-data/unit/check-abstract.test @@ -542,8 +542,8 @@ class C(A, B): pass x: C x.f() x.g() -x.f(x) # E: Too many arguments for "f" of "A" -x.g(x) # E: Too many arguments for "g" of "B" +x.f(x) # E: Too many positional arguments for "f" of "A" +x.g(x) # E: Too many positional arguments for "g" of "B" [case testInstantiatingAbstractClassWithMultipleBaseClasses] diff --git a/test-data/unit/check-basic.test b/test-data/unit/check-basic.test index bfd5e4e59303..cbc2e612cd55 100644 --- a/test-data/unit/check-basic.test +++ b/test-data/unit/check-basic.test @@ -44,7 +44,7 @@ if int(): import typing object(object()) [out] -main:2: error: Too many arguments for "object" +main:2: error: Too many positional arguments for "object" [case testVarDefWithInit] import typing @@ -115,7 +115,7 @@ f(object()) f(object(), object(), object()) [out] main:3: error: Missing positional argument "y" in call to "f" -main:4: error: Too many arguments for "f" +main:4: error: Too many positional arguments for "f" [case testMissingPositionalArguments] class Foo: diff --git a/test-data/unit/check-callable.test b/test-data/unit/check-callable.test index f3ec6ec5f939..da0f5f43bb2d 100644 --- a/test-data/unit/check-callable.test +++ b/test-data/unit/check-callable.test @@ -557,20 +557,20 @@ Some.cls_method(1) s.st_method(1) Some.st_method(1) -s.method(1, 2) # E: Too many arguments for "method" of "Some" -s.cls_method(1, 2) # E: Too many arguments for "cls_method" of "Some" -Some.cls_method(1, 2) # E: Too many arguments for "cls_method" of "Some" -s.st_method(1, 2) # E: Too many arguments for "st_method" of "Some" -Some.st_method(1, 2) # E: Too many arguments for "st_method" of "Some" +s.method(1, 2) # E: Too many positional arguments for "method" of "Some" +s.cls_method(1, 2) # E: Too many positional arguments for "cls_method" of "Some" +Some.cls_method(1, 2) # E: Too many positional arguments for "cls_method" of "Some" +s.st_method(1, 2) # E: Too many positional arguments for "st_method" of "Some" +Some.st_method(1, 2) # E: Too many positional arguments for "st_method" of "Some" -s.bad_method(1) # E: Too many arguments for "bad_method" of "Some" \ +s.bad_method(1) # E: Too many positional arguments for "bad_method" of "Some" \ # N: Looks like the first special argument in a method is not named "self", "cls", or "mcs", maybe it is missing? -s.bad_cls_method(1) # E: Too many arguments for "bad_cls_method" of "Some" \ +s.bad_cls_method(1) # E: Too many positional arguments for "bad_cls_method" of "Some" \ # N: Looks like the first special argument in a method is not named "self", "cls", or "mcs", maybe it is missing? -Some.bad_cls_method(1) # E: Too many arguments for "bad_cls_method" of "Some" \ +Some.bad_cls_method(1) # E: Too many positional arguments for "bad_cls_method" of "Some" \ # N: Looks like the first special argument in a method is not named "self", "cls", or "mcs", maybe it is missing? -s.bad_st_method(1) # E: Too many arguments for "bad_st_method" of "Some" -Some.bad_st_method(1) # E: Too many arguments for "bad_st_method" of "Some" +s.bad_st_method(1) # E: Too many positional arguments for "bad_st_method" of "Some" +Some.bad_st_method(1) # E: Too many positional arguments for "bad_st_method" of "Some" [builtins fixtures/callable.pyi] [case testClassMethodAliasStub] diff --git a/test-data/unit/check-class-namedtuple.test b/test-data/unit/check-class-namedtuple.test index c9b5b0f4db1a..039f29332a3d 100644 --- a/test-data/unit/check-class-namedtuple.test +++ b/test-data/unit/check-class-namedtuple.test @@ -46,7 +46,7 @@ x = X(1, '2') x.x x.z # E: "X" has no attribute "z" x = X(1) # E: Missing positional argument "y" in call to "X" -x = X(1, '2', 3) # E: Too many arguments for "X" +x = X(1, '2', 3) # E: Too many positional arguments for "X" [builtins fixtures/tuple.pyi] [case testNewNamedTupleShouldBeSingleBase] @@ -468,7 +468,7 @@ Y(y=1, x='1').method() class CallsBaseInit(X): def __init__(self, x: str) -> None: - super().__init__(x) # E: Too many arguments for "__init__" of "object" + super().__init__(x) # E: Too many positional arguments for "__init__" of "object" [builtins fixtures/tuple.pyi] [case testNewNamedTupleWithMethods] diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 7e00017a088f..b659cefa44cb 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -51,7 +51,7 @@ a.foo() # Fail a.foo(object(), A()) # Fail [out] main:5: error: Missing positional argument "x" in call to "foo" of "A" -main:6: error: Too many arguments for "foo" of "A" +main:6: error: Too many positional arguments for "foo" of "A" main:6: error: Argument 1 to "foo" of "A" has incompatible type "object"; expected "A" [case testMethodBody] @@ -960,7 +960,7 @@ class B(A): class C: pass A() # E: Missing positional argument "x" in call to "A" -B(C()) # E: Too many arguments for "B" +B(C()) # E: Too many positional arguments for "B" A(C()) B() @@ -1221,7 +1221,7 @@ class A: A.f(A()) A.f(object()) # E: Argument 1 to "f" of "A" has incompatible type "object"; expected "A" A.f() # E: Missing positional argument "self" in call to "f" of "A" -A.f(None, None) # E: Too many arguments for "f" of "A" \ +A.f(None, None) # E: Too many positional arguments for "f" of "A" \ # E: Argument 1 to "f" of "A" has incompatible type "None"; expected "A" [case testAccessAttributeViaClass] @@ -1348,7 +1348,7 @@ def f() -> None: def g(self) -> None: pass a: A a.g() - a.g(a) # E: Too many arguments for "g" of "A" + a.g(a) # E: Too many positional arguments for "g" of "A" [targets __main__, __main__.f] [case testGenericClassWithinFunction] @@ -1388,7 +1388,7 @@ class A: if int(): b = A() # E: Incompatible types in assignment (expression has type "A", variable has type "B") if int(): - b = B(b) # E: Too many arguments for "B" + b = B(b) # E: Too many positional arguments for "B" [out] [case testConstructNestedClassWithCustomInit] @@ -1422,7 +1422,7 @@ if int(): if int(): b = A() # E: Incompatible types in assignment (expression has type "A", variable has type "B") if int(): - b = A.B(b) # E: Too many arguments for "B" + b = A.B(b) # E: Too many positional arguments for "B" [case testAliasNestedClass] class Outer: @@ -1580,9 +1580,9 @@ class C: @classmethod def bar(cls) -> None: cls() - cls(1) # E: Too many arguments for "C" + cls(1) # E: Too many positional arguments for "C" cls.bar() - cls.bar(1) # E: Too many arguments for "bar" of "C" + cls.bar(1) # E: Too many positional arguments for "bar" of "C" cls.bozo() # E: "type[C]" has no attribute "bozo" [builtins fixtures/classmethod.pyi] [out] @@ -1593,7 +1593,7 @@ class C: @classmethod def foo(cls) -> None: pass C.foo() -C.foo(1) # E: Too many arguments for "foo" of "C" +C.foo(1) # E: Too many positional arguments for "foo" of "C" C.bozo() # E: "type[C]" has no attribute "bozo" [builtins fixtures/classmethod.pyi] @@ -1603,7 +1603,7 @@ class C: @classmethod def foo(cls) -> None: pass C().foo() -C().foo(1) # E: Too many arguments for "foo" of "C" +C().foo(1) # E: Too many positional arguments for "foo" of "C" C.bozo() # E: "type[C]" has no attribute "bozo" [builtins fixtures/classmethod.pyi] @@ -2148,7 +2148,7 @@ class D: def __set__(self, inst): pass class A: f = D() -A().f = 'x' # E: Too many arguments for "__set__" +A().f = 'x' # E: Too many positional arguments for "__set__" [case testDescriptorDunderSetTooManyArgs] class D: @@ -2176,7 +2176,7 @@ class D: def __get__(self, inst): pass class A: f = D() -A().f # E: Too many arguments for "__get__" +A().f # E: Too many positional arguments for "__get__" [case testDescriptorDunderGetTooManyArgs] class D: @@ -3401,7 +3401,7 @@ a = A() b = B() a() # E: Missing positional argument "x" in call to "__call__" of "A" -a(a, a) # E: Too many arguments for "__call__" of "A" +a(a, a) # E: Too many positional arguments for "__call__" of "A" if int(): a = a(a) if int(): @@ -3652,7 +3652,7 @@ from typing import Type class B: pass def f(A: Type[B]) -> None: - A(0) # E: Too many arguments for "B" + A(0) # E: Too many positional arguments for "B" A() [out] @@ -3687,7 +3687,7 @@ class B: T = TypeVar('T', bound=B) def f(A: Type[T]) -> None: A() - A(0) # E: Too many arguments for "B" + A(0) # E: Too many positional arguments for "B" [out] [case testTypeUsingTypeCTypeVarWithInit] @@ -3924,7 +3924,7 @@ tmp/foo.pyi:17: error: No overload variant of "User" matches argument type "str" tmp/foo.pyi:17: note: Possible overload variants: tmp/foo.pyi:17: note: def __init__(self) -> U tmp/foo.pyi:17: note: def __init__(self, arg: int) -> U -tmp/foo.pyi:18: error: Too many arguments for "foo" of "User" +tmp/foo.pyi:18: error: Too many positional arguments for "foo" of "User" [case testTypeUsingTypeCInUpperBound] from typing import TypeVar, Type @@ -5245,14 +5245,14 @@ class A(metaclass=MM): def h(a: Type[A], b: Type[object]) -> None: h(a, a) h(b, a) # E: Argument 1 to "h" has incompatible type "type[object]"; expected "type[A]" - a.f(1) # E: Too many arguments for "f" of "A" + a.f(1) # E: Too many positional arguments for "f" of "A" reveal_type(a.y) # N: Revealed type is "builtins.int" x = A # type: MM reveal_type(A.y) # N: Revealed type is "builtins.int" reveal_type(A.x) # N: Revealed type is "Any" -A.f(1) # E: Too many arguments for "f" of "A" -A().g(1) # E: Too many arguments for "g" of "A" +A.f(1) # E: Too many positional arguments for "f" of "A" +A().g(1) # E: Too many positional arguments for "g" of "A" [builtins fixtures/classmethod.pyi] [case testMetaclassTypeCallable] @@ -6123,7 +6123,7 @@ def decorate_forward_ref() -> Callable[[Type[A]], Type[A]]: ... @decorate(y=17) # E: Unexpected keyword argument "y" for "decorate" @decorate() # E: Missing positional argument "x" in call to "decorate" -@decorate(22, 25) # E: Too many arguments for "decorate" +@decorate(22, 25) # E: Too many positional arguments for "decorate" @decorate_forward_ref() @decorate(11) class A: pass diff --git a/test-data/unit/check-columns.test b/test-data/unit/check-columns.test index c822c7c44f41..3e58ff3ab0ee 100644 --- a/test-data/unit/check-columns.test +++ b/test-data/unit/check-columns.test @@ -24,7 +24,7 @@ A().f() A().f(1) A().f('') # E:7: Argument 1 to "f" of "A" has incompatible type "str"; expected "int" A().f(1, 1) # E:10: Argument 2 to "f" of "A" has incompatible type "int"; expected "str" -(A().f(1, 'hello', 'hi')) # E:2: Too many arguments for "f" of "A" +(A().f(1, 'hello', 'hi')) # E:2: Too many positional arguments for "f" of "A" [case testColumnsInvalidArgumentType] def f(x: int, y: str) -> None: ... diff --git a/test-data/unit/check-dataclass-transform.test b/test-data/unit/check-dataclass-transform.test index aed91dc672a6..a1af73fd14af 100644 --- a/test-data/unit/check-dataclass-transform.test +++ b/test-data/unit/check-dataclass-transform.test @@ -16,7 +16,7 @@ class Person: reveal_type(Person) # N: Revealed type is "def (name: builtins.str, age: builtins.int) -> __main__.Person" Person('John', 32) -Person('Jonh', 21, None) # E: Too many arguments for "Person" +Person('Jonh', 21, None) # E: Too many positional arguments for "Person" [typing fixtures/typing-full.pyi] [builtins fixtures/dataclasses.pyi] @@ -39,7 +39,7 @@ class Person: reveal_type(Person) # N: Revealed type is "def (name: builtins.str, age: builtins.int) -> __main__.Person" Person('John', 32) -Person('Jonh', 21, None) # E: Too many arguments for "Person" +Person('Jonh', 21, None) # E: Too many positional arguments for "Person" [typing fixtures/typing-full.pyi] [builtins fixtures/dataclasses.pyi] @@ -61,7 +61,7 @@ class Person: reveal_type(Person) # N: Revealed type is "def (name: builtins.str, age: builtins.int) -> __main__.Person" Person('John', 32) -Person('John', 21, None) # E: Too many arguments for "Person" +Person('John', 21, None) # E: Too many positional arguments for "Person" [typing fixtures/typing-full.pyi] [builtins fixtures/dataclasses.pyi] @@ -146,7 +146,8 @@ def always_use_kw(cls: Type) -> Type: class Baz: x: int Baz(x=5) -Baz(5) # E: Too many positional arguments for "Baz" +Baz(5) # E: Too many positional arguments for "Baz" \ + # E: Missing named argument "x" for "Baz" @dataclass_transform(order_default=True) def ordered(*, eq: bool = True) -> Callable[[Type], Type]: @@ -183,7 +184,8 @@ class KwOnly: class KwOptional: x: int -KwOnly(5) # E: Too many positional arguments for "KwOnly" +KwOnly(5) # E: Too many positional arguments for "KwOnly" \ + # E: Missing named argument "x" for "KwOnly" KwOptional(5) [typing fixtures/typing-full.pyi] @@ -379,7 +381,7 @@ class B: reveal_type(A) # N: Revealed type is "def (a: builtins.int) -> __main__.A" reveal_type(B) # N: Revealed type is "def (b: builtins.int) -> __main__.B" -A(1, "hello") # E: Too many arguments for "A" +A(1, "hello") # E: Too many positional arguments for "A" a = A(1) a.a = 2 # E: Property "a" defined in "A" is read-only @@ -406,7 +408,7 @@ class B: reveal_type(A) # N: Revealed type is "def (a: builtins.int) -> __main__.A" reveal_type(B) # N: Revealed type is "def (b: builtins.int) -> __main__.B" -A(1, "hello") # E: Too many arguments for "A" +A(1, "hello") # E: Too many positional arguments for "A" a = A(1) a.a = 2 # E: Property "a" defined in "A" is read-only @@ -426,7 +428,9 @@ class Person(Dataclass, kw_only=True): age: int reveal_type(Person) # N: Revealed type is "def (*, name: builtins.str, age: builtins.int) -> __main__.Person" -Person('Jonh', 21) # E: Too many positional arguments for "Person" +Person('Jonh', 21) # E: Too many positional arguments for "Person" \ + # E: Missing named argument "name" for "Person" \ + # E: Missing named argument "age" for "Person" person = Person(name='John', age=32) person.name = "John Smith" # E: Property "name" defined in "Person" is read-only @@ -455,7 +459,9 @@ class Person(BaseDataclass, kw_only=True): age: int reveal_type(Person) # N: Revealed type is "def (*, name: builtins.str, age: builtins.int) -> __main__.Person" -Person('Jonh', 21) # E: Too many positional arguments for "Person" +Person('Jonh', 21) # E: Too many positional arguments for "Person" \ + # E: Missing named argument "name" for "Person" \ + # E: Missing named argument "age" for "Person" person = Person(name='John', age=32) person.name = "John Smith" # E: Property "name" defined in "Person" is read-only @@ -482,7 +488,7 @@ class Foo(metaclass=SubMeta): foo: int reveal_type(Foo) # N: Revealed type is "def () -> __main__.Foo" -Foo(1) # E: Too many arguments for "Foo" +Foo(1) # E: Too many positional arguments for "Foo" [typing fixtures/typing-full.pyi] [builtins fixtures/dataclasses.pyi] diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index 3cc4a03ffb11..5efd5135439a 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -11,7 +11,7 @@ class Person: reveal_type(Person) # N: Revealed type is "def (name: builtins.str, age: builtins.int) -> __main__.Person" Person('John', 32) -Person('Jonh', 21, None) # E: Too many arguments for "Person" +Person('Jonh', 21, None) # E: Too many positional arguments for "Person" [builtins fixtures/dataclasses.pyi] [typing fixtures/typing-medium.pyi] @@ -47,7 +47,7 @@ class Person(Mammal): reveal_type(Person) # N: Revealed type is "def (age: builtins.int, name: builtins.str) -> __main__.Person" Mammal(10) Person(32, 'John') -Person(21, 'Jonh', None) # E: Too many arguments for "Person" +Person(21, 'Jonh', None) # E: Too many positional arguments for "Person" [builtins fixtures/dataclasses.pyi] [typing fixtures/typing-medium.pyi] @@ -154,7 +154,7 @@ reveal_type(Person) # N: Revealed type is "def (age: builtins.int, name: builti reveal_type(SpecialPerson) # N: Revealed type is "def (age: builtins.int, name: builtins.str, special_factor: builtins.float) -> __main__.SpecialPerson" reveal_type(ExtraSpecialPerson) # N: Revealed type is "def (age: builtins.int, name: builtins.str, special_factor: builtins.float) -> __main__.ExtraSpecialPerson" Person(32, 'John') -Person(21, 'John', None) # E: Too many arguments for "Person" +Person(21, 'John', None) # E: Too many positional arguments for "Person" SpecialPerson(21, 'John', 0.5) ExtraSpecialPerson(21, 'John', 0.5) @@ -389,11 +389,15 @@ class Application: Application(rating=5) Application(name='name', rating=5) Application() # E: Missing named argument "rating" for "Application" -Application('name') # E: Too many positional arguments for "Application" # E: Missing named argument "rating" for "Application" -Application('name', 123) # E: Too many positional arguments for "Application" +Application('name') # E: Too many positional arguments for "Application" \ + # E: Missing named argument "rating" for "Application" +Application('name', 123) # E: Too many positional arguments for "Application" \ + # E: Missing named argument "rating" for "Application" Application('name', rating=123) # E: Too many positional arguments for "Application" -Application(name=123, rating='name') # E: Argument "name" to "Application" has incompatible type "int"; expected "str" # E: Argument "rating" to "Application" has incompatible type "str"; expected "int" -Application(rating='name', name=123) # E: Argument "rating" to "Application" has incompatible type "str"; expected "int" # E: Argument "name" to "Application" has incompatible type "int"; expected "str" +Application(name=123, rating='name') # E: Argument "name" to "Application" has incompatible type "int"; expected "str" \ + # E: Argument "rating" to "Application" has incompatible type "str"; expected "int" +Application(rating='name', name=123) # E: Argument "rating" to "Application" has incompatible type "str"; expected "int" \ + # E: Argument "name" to "Application" has incompatible type "int"; expected "str" [builtins fixtures/dataclasses.pyi] @@ -411,8 +415,10 @@ Application('name', rating=123) Application(name='name', rating=5) Application() # E: Missing named argument "rating" for "Application" Application('name') # E: Missing named argument "rating" for "Application" -Application('name', 123) # E: Too many positional arguments for "Application" -Application(123, rating='name') # E: Argument 1 to "Application" has incompatible type "int"; expected "str" # E: Argument "rating" to "Application" has incompatible type "str"; expected "int" +Application('name', 123) # E: Too many positional arguments for "Application" \ + # E: Missing named argument "rating" for "Application" +Application(123, rating='name') # E: Argument 1 to "Application" has incompatible type "int"; expected "str" \ + # E: Argument "rating" to "Application" has incompatible type "str"; expected "int" [builtins fixtures/dataclasses.pyi] @@ -446,8 +452,10 @@ class Application: Application(rating=5) Application(name='name', rating=5) Application() # E: Missing named argument "rating" for "Application" -Application('name') # E: Too many positional arguments for "Application" # E: Missing named argument "rating" for "Application" -Application('name', 123) # E: Too many positional arguments for "Application" +Application('name') # E: Too many positional arguments for "Application" \ + # E: Missing named argument "rating" for "Application" +Application('name', 123) # E: Too many positional arguments for "Application" \ + # E: Missing named argument "rating" for "Application" Application('name', rating=123) # E: Too many positional arguments for "Application" [builtins fixtures/dataclasses.pyi] @@ -467,8 +475,7 @@ Application() # E: Missing positional argument "rating" in call to "Application Application(123) Application('name') # E: Argument 1 to "Application" has incompatible type "str"; expected "int" Application('name', 123) # E: Too many positional arguments for "Application" \ - # E: Argument 1 to "Application" has incompatible type "str"; expected "int" \ - # E: Argument 2 to "Application" has incompatible type "int"; expected "str" + # E: Argument 1 to "Application" has incompatible type "str"; expected "int" Application(123, rating=123) # E: "Application" gets multiple values for keyword argument "rating" [builtins fixtures/dataclasses.pyi] @@ -1019,7 +1026,7 @@ class C(B): A(1) B(1) -B(1, 2) # E: Too many arguments for "B" +B(1, 2) # E: Too many positional arguments for "B" C(1, 2) C(1, 'a') # E: Argument 2 to "C" has incompatible type "str"; expected "int" diff --git a/test-data/unit/check-deprecated.test b/test-data/unit/check-deprecated.test index 607e9d767956..bdcc1ea27ed4 100644 --- a/test-data/unit/check-deprecated.test +++ b/test-data/unit/check-deprecated.test @@ -51,7 +51,7 @@ def f() -> None: ... f # E: function __main__.f is deprecated: use f2 instead # type: ignore[deprecated] f(1) # E: function __main__.f is deprecated: use f2 instead \ - # E: Too many arguments for "f" + # E: Too many positional arguments for "f" f[1] # E: function __main__.f is deprecated: use f2 instead \ # E: Value of type "Callable[[], None]" is not indexable g = f # E: function __main__.f is deprecated: use f2 instead @@ -116,7 +116,7 @@ C.missing() # E: class __main__.C is deprecated: use C2 instead \ # E: "type[C]" has no attribute "missing" C.__init__(c) # E: class __main__.C is deprecated: use C2 instead C(1) # E: class __main__.C is deprecated: use C2 instead \ - # E: Too many arguments for "C" + # E: Too many positional arguments for "C" D = C # E: class __main__.C is deprecated: use C2 instead D() @@ -604,7 +604,7 @@ C.f # E: function __main__.C.f is deprecated: use g instead C().f # E: function __main__.C.f is deprecated: use g instead C().f() # E: function __main__.C.f is deprecated: use g instead C().f(1) # E: function __main__.C.f is deprecated: use g instead \ - # E: Too many arguments for "f" of "C" + # E: Too many positional arguments for "f" of "C" f = C().f # E: function __main__.C.f is deprecated: use g instead f() t = (C.f, C.f, C.g) # E: function __main__.C.f is deprecated: use g instead diff --git a/test-data/unit/check-dynamic-typing.test b/test-data/unit/check-dynamic-typing.test index eeaa69affb78..56c32badaa04 100644 --- a/test-data/unit/check-dynamic-typing.test +++ b/test-data/unit/check-dynamic-typing.test @@ -151,6 +151,10 @@ class type: pass class function: pass class str: pass class dict: pass +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass [case testBinaryOperationsWithDynamicAsRightOperand] from typing import Any @@ -224,6 +228,10 @@ class type: pass class function: pass class str: pass class dict: pass +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass [case testDynamicWithUnaryExpressions] from typing import Any @@ -375,7 +383,7 @@ h: Callable[[A], None] def f(x): pass f() # E: Missing positional argument "x" in call to "f" -f(x, x) # E: Too many arguments for "f" +f(x, x) # E: Too many positional arguments for "f" if int(): g = f # E: Incompatible types in assignment (expression has type "Callable[[Any], Any]", variable has type "Callable[[], None]") f(a) @@ -432,9 +440,9 @@ g2: Callable[[A, A], None] g3: Callable[[A, A, A], None] g4: Callable[[A, A, A, A], None] -f01(a, a) # E: Too many arguments for "f01" +f01(a, a) # E: Too many positional arguments for "f01" f13() # E: Missing positional argument "x" in call to "f13" -f13(a, a, a, a) # E: Too many arguments for "f13" +f13(a, a, a, a) # E: Too many positional arguments for "f13" if int(): g2 = f01 # E: Incompatible types in assignment (expression has type "Callable[[Any], Any]", variable has type "Callable[[A, A], None]") if int(): diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index 31b9c526b6e5..d78eb7208075 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -248,7 +248,7 @@ main:6: error: Invalid "type: ignore" comment [syntax] [case testErrorCodeArgKindAndCount] def f(x: int) -> None: pass # N: "f" defined here f() # E: Missing positional argument "x" in call to "f" [call-arg] -f(1, 2) # E: Too many arguments for "f" [call-arg] +f(1, 2) # E: Too many positional arguments for "f" [call-arg] f(y=1) # E: Unexpected keyword argument "y" for "f" [call-arg] def g(*, x: int) -> None: pass diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test index 30b1f1a68e15..0013d2d209e8 100644 --- a/test-data/unit/check-expressions.test +++ b/test-data/unit/check-expressions.test @@ -791,7 +791,7 @@ divmod() # E: "divmod" expects 2 arguments \ divmod(7) # E: "divmod" expects 2 arguments \ # E: Missing positional argument "_y" in call to "divmod" divmod(7, 8, 9) # E: "divmod" expects 2 arguments \ - # E: Too many arguments for "divmod" + # E: Too many positional arguments for "divmod" divmod(_x=7, _y=9) # E: "divmod" must be called with 2 positional arguments divmod('foo', 'foo') # E: Unsupported left operand type for divmod ("str") @@ -1568,7 +1568,7 @@ class A: def __add__(self) -> 'A': pass [out] -main:3: error: Too many arguments for "__add__" of "A" +main:3: error: Too many positional arguments for "__add__" of "A" [case testOperatorMethodAsVar] from typing import Any @@ -2561,3 +2561,29 @@ def last_known_value() -> None: x, y, z = xy # E: Unpacking a string is disallowed reveal_type(z) # N: Revealed type is "builtins.str" [builtins fixtures/primitives.pyi] + +[case testTupleExpressionWithVariadicVarArg] +def test(*args: int) -> None: + reveal_type( args ) # N: Revealed type is "builtins.tuple[builtins.int, ...]" + reveal_type( (*args,) ) # N: Revealed type is "builtins.tuple[builtins.int, ...]" +[builtins fixtures/tuple.pyi] + + +[case testTupleExpressionWithTypeVarTupleVarArg] +from typing_extensions import TypeVarTuple, Unpack +Ts = TypeVarTuple('Ts') + +def test(*args: Unpack[Ts]) -> None: + reveal_type( args ) # N: Revealed type is "tuple[Unpack[Ts`-1]]" + reveal_type( (*args,) ) # N: Revealed type is "tuple[Unpack[Ts`-1]]" +[builtins fixtures/tuple.pyi] + +[case testTupleExpressionWithParamSpecVarArg] +from typing import Callable +from typing_extensions import ParamSpec, Unpack +P = ParamSpec('P') + +def test(fn: Callable[P, None], *args: P.args, **kwargs: P.kwargs) -> None: + reveal_type( args ) # N: Revealed type is "P.args`-1" + reveal_type( (*args,) ) # N: Revealed type is "P.args`-1" +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-fastparse.test b/test-data/unit/check-fastparse.test index f1c0716e9fb8..cd8049e28075 100644 --- a/test-data/unit/check-fastparse.test +++ b/test-data/unit/check-fastparse.test @@ -183,7 +183,7 @@ def f(): # E: Type signature has too many arguments pass f() -f(1) # E: Too many arguments for "f" +f(1) # E: Too many positional arguments for "f" [case testFasterParseTooFewArgumentsAnnotation] def f(x, y): # E: Type signature has too few arguments diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index a892aeba5a2c..6271c017199a 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -2206,7 +2206,7 @@ x.trim() def bad_return_type() -> str: return None -bad_return_type('no args taken!') # E: Too many arguments for "bad_return_type" [call-arg] +bad_return_type('no args taken!') # E: Too many positional arguments for "bad_return_type" [call-arg] [case testEnableErrorCode] # flags: --disable-error-code attr-defined --enable-error-code attr-defined --show-error-codes diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 55b87a27da34..94213a57dd7f 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -20,7 +20,7 @@ if int(): if int(): b = f() # E: Too few arguments if int(): - b = f(a, a) # E: Too many arguments + b = f(a, a) # E: Too many positional arguments if int(): b = f(a) @@ -503,7 +503,7 @@ if int(): if int(): b = f(b) # E: Argument 1 to "f" has incompatible type "B"; expected "A | None" if int(): - b = f(a, a) # E: Too many arguments for "f" + b = f(a, a) # E: Too many positional arguments for "f" if int(): b = f() @@ -605,7 +605,7 @@ class A: a: A a.f() a.g(B()) -a.f(a) # E: Too many arguments +a.f(a) # E: Too many positional arguments a.g() # E: Too few arguments [case testMethodWithInvalidMethodAsDataAttribute] @@ -627,7 +627,7 @@ class A: f = x # type: ClassVar[Callable[[Any], Any]] a: A a.f() -a.f(a) # E: Too many arguments +a.f(a) # E: Too many positional arguments [case testMethodWithInferredMethodAsDataAttribute] from typing import Any @@ -667,7 +667,7 @@ class A: g = f a: A a.g(object()) -a.g(a, a) # E: Too many arguments +a.g(a, a) # E: Too many positional arguments a.g() # E: Too few arguments [case testMethodAsDataAttributeInGenericClass] @@ -716,8 +716,8 @@ class A: a = A(lambda: None) a.f() a.g() -a.f(a) # E: Too many arguments -a.g(a) # E: Too many arguments +a.f(a) # E: Too many positional arguments +a.g(a) # E: Too many positional arguments -- Nested functions @@ -813,7 +813,7 @@ def f() -> None: g(1) # E [out] main:4: error: Argument 1 to "h" has incompatible type "str"; expected "int" -main:7: error: Too many arguments for "g" +main:7: error: Too many positional arguments for "g" [case testMutuallyRecursiveDecoratedFunctions] from typing import Callable, Any @@ -912,7 +912,7 @@ def dec(x) -> Callable[[], None]: pass @dec def f(y): pass f() -f(None) # E: Too many arguments for "f" +f(None) # E: Too many positional arguments for "f" [case testDecoratorThatSwitchesTypeWithMethod] from typing import Any, Callable @@ -922,7 +922,7 @@ class A: def f(self, a, b, c): pass a: A a.f() -a.f(None) # E: Too many arguments for "f" of "A" +a.f(None) # E: Too many positional arguments for "f" of "A" [case testNestedDecorators] from typing import Any, Callable @@ -932,7 +932,7 @@ def dec2(f: Callable[[Any, Any], None]) -> Callable[[Any], None]: pass @dec2 def f(x, y): pass f() -f(None) # E: Too many arguments for "f" +f(None) # E: Too many positional arguments for "f" [case testInvalidDecorator1] from typing import Any, Callable @@ -1045,7 +1045,7 @@ from typing import TypeVar def f(self) -> None: g() - g(1) # E: Too many arguments for "g" + g(1) # E: Too many positional arguments for "g" h(1).x # E: "str" has no attribute "x" h('') # E: Argument 1 to "h" has incompatible type "str"; expected "int" @@ -1553,7 +1553,7 @@ f = g if g(): def f(): pass f() -f(1) # E: Too many arguments +f(1) # E: Too many positional arguments [case testRedefineFunctionDefinedAsVariableInitializedToNone] def g(): pass @@ -1561,7 +1561,7 @@ f = None if g(): def f(): pass f() -f(1) # E: Too many arguments for "f" +f(1) # E: Too many positional arguments for "f" [case testRedefineNestedFunctionDefinedAsVariableInitializedToNone] def g() -> None: @@ -1935,7 +1935,8 @@ from mypy_extensions import NamedArg def a(f: Callable[[NamedArg(int, 'x')], int]): f(x=4) - f(2) # E: Too many positional arguments + f(2) # E: Too many positional arguments \ + # E: Missing named argument "x" f() # E: Missing named argument "x" f(y=3) # E: Unexpected keyword argument "y" f(x="foo") # E: Argument "x" has incompatible type "str"; expected "int" @@ -1959,7 +1960,7 @@ from mypy_extensions import KwArg def a(f: Callable[[KwArg(int)], int]): f(x=4) - f(2) # E: Too many arguments + f(2) # E: Too many positional arguments f() f(y=3) f(x=4, y=3, z=10) @@ -2268,7 +2269,7 @@ a = 1 # type: Any with a: def f() -> None: pass - f(1) # E: Too many arguments for "f" + f(1) # E: Too many positional arguments for "f" [case testNameForDecoratorMethod] @@ -2294,7 +2295,7 @@ def g(x: int) -> None: pass h = f if bool() else g reveal_type(h) # N: Revealed type is "(def ()) | (def (x: builtins.int))" -h(7) # E: Too many arguments for "f" +h(7) # E: Too many positional arguments for "f" T = TypeVar("T") def join(x: T, y: T) -> T: ... diff --git a/test-data/unit/check-functools.test b/test-data/unit/check-functools.test index ffd0a97b6988..ab6701e092ee 100644 --- a/test-data/unit/check-functools.test +++ b/test-data/unit/check-functools.test @@ -169,7 +169,7 @@ p2("a") # OK p2("a", 3) # OK p2("a", c=3) # OK p2(1, 3) # E: Argument 1 to "foo" has incompatible type "int"; expected "str" -p2(1, "a", 3) # E: Too many arguments for "foo" \ +p2(1, "a", 3) # E: Too many positional arguments for "foo" \ # E: Argument 1 to "foo" has incompatible type "int"; expected "str" \ # E: Argument 2 to "foo" has incompatible type "str"; expected "int" p2(a=1, b="a", c=3) # E: Unexpected keyword argument "a" for "foo" @@ -179,8 +179,7 @@ p3(1) # OK p3(1, c=3) # OK p3(a=1) # OK p3(1, b="a", c=3) # OK, keywords can be clobbered -p3(1, 3) # E: Too many positional arguments for "foo" \ - # E: Argument 2 to "foo" has incompatible type "int"; expected "str" +p3(1, 3) # E: Too many positional arguments for "foo" functools.partial(foo, "a") # E: Argument 1 to "foo" has incompatible type "str"; expected "int" functools.partial(foo, b=1) # E: Argument "b" to "foo" has incompatible type "int"; expected "str" @@ -215,14 +214,16 @@ def bar(*a: bytes, **k: int): p1("a", d="a", **k) p1("a", **k) # E: Argument 2 to "foo" has incompatible type "**dict[str, int]"; expected "str" p1(**k) # E: Argument 1 to "foo" has incompatible type "**dict[str, int]"; expected "str" - p1(*a) # E: Expected iterable as variadic argument + reveal_type(a) # N: Revealed type is "builtins.tuple[builtins.bytes, ...]" + p1(*a) # E: Argument 1 to "foo" has incompatible type "*tuple[bytes, ...]"; expected "str" \ + # E: Argument 1 to "foo" has incompatible type "*tuple[bytes, ...]"; expected "int" def baz(a: int, b: int) -> int: ... def test_baz(xs: List[int]): p3 = functools.partial(baz, *xs) p3() - p3(1) # E: Too many arguments for "baz" + p3(1) # E: Too many positional arguments for "baz" [builtins fixtures/dict.pyi] @@ -497,15 +498,13 @@ def main5(**d2: Unpack[D2]) -> None: def main6(a2good: A2Good, a2bad: A2Bad, **d1: Unpack[D1]) -> None: partial(fn3, **d1)() # E: Missing positional argument "a1" in call to "fn3" partial(fn3, **d1)("asdf") # E: Too many positional arguments for "fn3" \ - # E: Too few arguments for "fn3" \ - # E: Argument 1 to "fn3" has incompatible type "str"; expected "int" + # E: Too few arguments for "fn3" partial(fn3, **d1)(a2="asdf") partial(fn3, **d1)(**a2good) partial(fn3, **d1)(**a2bad) # E: Argument "a2" to "fn3" has incompatible type "int"; expected "str" partial(fn4, **d1)() - partial(fn4, **d1)("asdf") # E: Too many positional arguments for "fn4" \ - # E: Argument 1 to "fn4" has incompatible type "str"; expected "int" + partial(fn4, **d1)("asdf") # E: Too many positional arguments for "fn4" partial(fn4, **d1)(a2="asdf") partial(fn4, **d1)(**a2good) partial(fn4, **d1)(**a2bad) # E: Argument "a2" to "fn4" has incompatible type "int"; expected "str" @@ -530,8 +529,7 @@ reveal_type(first_kw(args=[1])) # N: Revealed type is "builtins.int" # TODO: this is indeed invalid, but the error is incomprehensible. first_kw([1]) # E: Too many positional arguments for "get" \ - # E: Too few arguments for "get" \ - # E: Argument 1 to "get" has incompatible type "list[int]"; expected "int" + # E: Too few arguments for "get" [builtins fixtures/list.pyi] [case testFunctoolsPartialHigherOrder] diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 32975350e20a..a892f2cb8e28 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -441,7 +441,7 @@ class Node(Generic[T]): def __init__(self, x: T) -> None: ... Node[int]() # E: Missing positional argument "x" in call to "Node" -Node[int](1, 1, 1) # E: Too many arguments for "Node" +Node[int](1, 1, 1) # E: Too many positional arguments for "Node" [out] [case testTypeApplicationTvars] diff --git a/test-data/unit/check-ignore.test b/test-data/unit/check-ignore.test index 0c373c0e2788..bcdc0af518f6 100644 --- a/test-data/unit/check-ignore.test +++ b/test-data/unit/check-ignore.test @@ -120,7 +120,7 @@ from m import B # type: ignore class C(B): def f(self) -> None: - self.f(1) # E: Too many arguments for "f" of "C" + self.f(1) # E: Too many positional arguments for "f" of "C" self.g(1) [out] @@ -131,7 +131,7 @@ class C(m.B): def f(self) -> None: ... c = C() -c.f(1) # E: Too many arguments for "f" of "C" +c.f(1) # E: Too many positional arguments for "f" of "C" c.g(1) c.x = 1 [out] @@ -143,7 +143,7 @@ class C(m.B): @staticmethod def f() -> None: pass -C.f(1) # E: Too many arguments for "f" of "C" +C.f(1) # E: Too many positional arguments for "f" of "C" C.g(1) C.x = 1 [builtins fixtures/staticmethod.pyi] @@ -155,7 +155,7 @@ from m import B # type: ignore class C: pass class D(C, B): def f(self) -> None: - self.f(1) # E: Too many arguments for "f" of "D" + self.f(1) # E: Too many positional arguments for "f" of "D" self.g(1) [out] @@ -165,7 +165,7 @@ from m import B # type: ignore class C(B): pass class D(C): def f(self) -> None: - self.f(1) # E: Too many arguments for "f" of "D" + self.f(1) # E: Too many positional arguments for "f" of "D" self.g(1) [out] diff --git a/test-data/unit/check-incomplete-fixture.test b/test-data/unit/check-incomplete-fixture.test index 146494df1bd6..657814bab0cd 100644 --- a/test-data/unit/check-incomplete-fixture.test +++ b/test-data/unit/check-incomplete-fixture.test @@ -42,24 +42,30 @@ main:1: error: Name "isinstance" is not defined main:1: note: Maybe your test fixture does not define "builtins.isinstance"? main:1: note: Consider adding [builtins fixtures/isinstancelist.pyi] to your test description -[case testTupleMissingFromStubs1] -tuple() +[case testSetMissingFromStubs1] +set() [out] -main:1: error: Name "tuple" is not defined -main:1: note: Maybe your test fixture does not define "builtins.tuple"? -main:1: note: Consider adding [builtins fixtures/tuple.pyi] to your test description -main:1: note: Did you forget to import it from "typing"? (Suggestion: "from typing import Tuple") +main:1: error: Name "set" is not defined +main:1: note: Maybe your test fixture does not define "builtins.set"? +main:1: note: Consider adding [builtins fixtures/set.pyi] to your test description +main:1: note: Did you forget to import it from "typing"? (Suggestion: "from typing import Set") -[case testTupleMissingFromStubs2] -tuple() -from typing import Tuple -x: Tuple[int, str] +[case testSetMissingFromStubs2] +set() +from typing import Set +x: set[int] [out] -main:1: error: Name "tuple" is not defined -main:1: note: Maybe your test fixture does not define "builtins.tuple"? -main:1: note: Consider adding [builtins fixtures/tuple.pyi] to your test description -main:1: note: Did you forget to import it from "typing"? (Suggestion: "from typing import Tuple") -main:3: error: Name "tuple" is not defined +main:1: error: Name "set" is not defined +main:1: note: Maybe your test fixture does not define "builtins.set"? +main:1: note: Consider adding [builtins fixtures/set.pyi] to your test description +main:1: note: Did you forget to import it from "typing"? (Suggestion: "from typing import Set") +main:2: error: Module "typing" has no attribute "Set" +main:2: note: Maybe your test fixture does not define "builtins.set"? +main:2: note: Consider adding [builtins fixtures/set.pyi] to your test description +main:3: error: Name "set" is not defined +main:3: note: Maybe your test fixture does not define "builtins.set"? +main:3: note: Consider adding [builtins fixtures/set.pyi] to your test description +main:3: note: Did you forget to import it from "typing"? (Suggestion: "from typing import Set") [case testClassmethodMissingFromStubs] class A: diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index 46cf828ef52c..1f7b07eac412 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -2247,7 +2247,7 @@ def foo(x) -> int: [rechecked2 m, n] [stale2 m, n] [out2] -tmp/n.py:2: error: Too many arguments for "foo" +tmp/n.py:2: error: Too many positional arguments for "foo" [out3] [case testCacheDeletedAfterErrorsFound] @@ -6951,14 +6951,13 @@ tmp/a.py:8: note: Revealed type is "functools.partial[builtins.int]" tmp/a.py:13: error: Argument 1 to "takes_callable_str" has incompatible type "partial[int]"; expected "Callable[..., str]" tmp/a.py:13: note: "partial[int].__call__" has type "def __call__(__self, *args: Any, **kwargs: Any) -> int" tmp/a.py:18: error: Argument 1 to "foo" has incompatible type "int"; expected "str" -tmp/a.py:19: error: Too many arguments for "foo" +tmp/a.py:19: error: Too many positional arguments for "foo" tmp/a.py:19: error: Argument 1 to "foo" has incompatible type "int"; expected "str" tmp/a.py:19: error: Argument 2 to "foo" has incompatible type "str"; expected "int" tmp/a.py:20: error: Unexpected keyword argument "a" for "foo" tmp/partial.py:4: note: "foo" defined here [out2] tmp/a.py:8: error: Too many positional arguments for "foo" -tmp/a.py:8: error: Argument 2 to "foo" has incompatible type "int"; expected "str" [case testStartUsingTypeGuard] diff --git a/test-data/unit/check-inference-context.test b/test-data/unit/check-inference-context.test index b9e7e6996ade..9b6ea413d119 100644 --- a/test-data/unit/check-inference-context.test +++ b/test-data/unit/check-inference-context.test @@ -628,7 +628,7 @@ reveal_type((lambda x, s, i: x)(1.0, i=0, s='x')) # N: Revealed type is "builtin if object(): (lambda x, s, i: x)() # E: Too few arguments if object(): - (lambda: 0)(1) # E: Too many arguments + (lambda: 0)(1) # E: Too many positional arguments -- varargs are not handled, but it should not crash reveal_type((lambda *k, s, i: i)(type, i=0, s='x')) # N: Revealed type is "Any" reveal_type((lambda s, *k, i: i)(i=0, s='x')) # N: Revealed type is "Any" diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 7f970487bbe7..6ac14f6ba0a2 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -151,7 +151,7 @@ class B: pass import typing def f() -> None: a = A - a(A()) # E: Too many arguments + a(A()) # E: Too many positional arguments a() t = a # type: type diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index ff403a9f5d8c..98386137d2a2 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -1931,10 +1931,10 @@ if issubclass(y): # E: Missing positional argument "t" in call to "issubclass" [builtins fixtures/isinstancelist.pyi] [case testIsInstanceTooManyArgs] -isinstance(1, 1, 1) # E: Too many arguments for "isinstance" \ +isinstance(1, 1, 1) # E: Too many positional arguments for "isinstance" \ # E: Argument 2 to "isinstance" has incompatible type "int"; expected "type | tuple[Any, ...]" x: object -if isinstance(x, str, 1): # E: Too many arguments for "isinstance" +if isinstance(x, str, 1): # E: Too many positional arguments for "isinstance" reveal_type(x) # N: Revealed type is "builtins.object" x = 1 reveal_type(x) # N: Revealed type is "builtins.int" @@ -2334,7 +2334,7 @@ def f(var: Union[int, str]) -> None: [case testIsInstanceWithWrongStarExpression] var = 'some string' -if isinstance(var, *(str, int)): # E: Too many arguments for "isinstance" +if isinstance(var, *(str, int)): # E: Too many positional arguments for "isinstance" pass [builtins fixtures/isinstancelist.pyi] diff --git a/test-data/unit/check-kwargs.test b/test-data/unit/check-kwargs.test index 4099716bcf6b..86dce761f305 100644 --- a/test-data/unit/check-kwargs.test +++ b/test-data/unit/check-kwargs.test @@ -166,8 +166,10 @@ f(A(), B()) # E: Too many positional arguments for "f" g(A(), b=B()) g(b=B(), a=A()) g(A()) # E: Missing named argument "b" for "g" -g(A(), B()) # E: Too many positional arguments for "g" -h(A()) # E: Missing named argument "b" for "h" # E: Missing named argument "aa" for "h" +g(A(), B()) # E: Too many positional arguments for "g" \ + # E: Missing named argument "b" for "g" +h(A()) # E: Missing named argument "b" for "h" \ + # E: Missing named argument "aa" for "h" h(A(), b=B()) # E: Missing named argument "aa" for "h" h(A(), aa=A()) # E: Missing named argument "b" for "h" h(A(), b=B(), aa=A()) @@ -196,8 +198,10 @@ f(A(), B()) # E: Too many positional arguments for "f" g(A(), b=B()) g(b=B(), a=A()) g(A()) # E: Missing named argument "b" for "g" -g(A(), B()) # E: Too many positional arguments for "g" -h(A()) # E: Missing named argument "b" for "h" # E: Missing named argument "aa" for "h" +g(A(), B()) # E: Too many positional arguments for "g" \ + # E: Missing named argument "b" for "g" +h(A()) # E: Missing named argument "b" for "h" \ + # E: Missing named argument "aa" for "h" h(A(), b=B()) # E: Missing named argument "aa" for "h" h(A(), aa=A()) # E: Missing named argument "b" for "h" h(A(), b=B(), aa=A()) @@ -288,7 +292,7 @@ f() f(x=A()) f(y=A(), z=A()) f(x=B()) # E: Argument "x" to "f" has incompatible type "B"; expected "A" -f(A()) # E: Too many arguments for "f" +f(A()) # E: Too many positional arguments for "f" # Perhaps a better message would be "Too many *positional* arguments..." [builtins fixtures/dict.pyi] @@ -457,7 +461,7 @@ f(**c) # E: Argument after ** must have string keys [case testCallStar2WithStar] def f(**k): pass -f(*(1, 2)) # E: Too many arguments for "f" +f(*(1, 2)) # E: Too many positional arguments for "f" [builtins fixtures/dict.pyi] [case testUnexpectedMethodKwargInNestedClass] diff --git a/test-data/unit/check-literal.test b/test-data/unit/check-literal.test index d71370eb4191..8fa1024de052 100644 --- a/test-data/unit/check-literal.test +++ b/test-data/unit/check-literal.test @@ -1174,7 +1174,9 @@ reveal_type(arr3) # N: Revealed type is "builtins.list[builtins.int]" reveal_type(arr4) # N: Revealed type is "builtins.list[builtins.object]" reveal_type(arr5) # N: Revealed type is "builtins.list[builtins.object]" -bad: List[Literal[1, 2]] = [1, 2, 3] # E: List item 2 has incompatible type "Literal[3]"; expected "Literal[1, 2]" +bad0: List[Literal[1, 2]] = [3, 1, 2] # E: List item 0 has incompatible type "Literal[3]"; expected "Literal[1, 2]" +bad1: List[Literal[1, 2]] = [1, 3, 2] # E: List item 1 has incompatible type "Literal[3]"; expected "Literal[1, 2]" +bad2: List[Literal[1, 2]] = [1, 2, 3] # E: List item 2 has incompatible type "Literal[3]"; expected "Literal[1, 2]" [builtins fixtures/list.pyi] [out] diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index fd81765f5624..bfeb35a63e89 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -67,7 +67,7 @@ if 1: m.a = m.b # E: Incompatible types in assignment (expression has type "B", variable has type "A") m.a = m.a m.f() - m.f(m.a) # E: Too many arguments for "f" + m.f(m.a) # E: Too many positional arguments for "f" m.a = m.A() m.a = m.B() # E: Incompatible types in assignment (expression has type "B", variable has type "A") [file m.py] @@ -86,7 +86,7 @@ def f() -> None: # E: Incompatible types in assignment (expression has type "B", variable has type "A") a = a f() - f(a) # E: Too many arguments for "f" + f(a) # E: Too many positional arguments for "f" a = A() a = B() \ # E: Incompatible types in assignment (expression has type "B", variable has type "A") @@ -107,7 +107,7 @@ class C: a = b # E: Incompatible types in assignment (expression has type "B", variable has type "A") a = a f() - f(a) # E: Too many arguments for "f" + f(a) # E: Too many positional arguments for "f" a = A() a = B() # E: Incompatible types in assignment (expression has type "B", variable has type "A") [file m.py] @@ -123,7 +123,7 @@ import typing class C: import m m.f() - m.f(C) # E: Too many arguments for "f" + m.f(C) # E: Too many positional arguments for "f" [file m.py] def f() -> None: pass [out] @@ -350,7 +350,7 @@ foobar('') [out] main:2: error: Cannot find implementation or library stub for module named "foobar" main:2: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports -main:4: error: Too many arguments for "foobar" +main:4: error: Too many positional arguments for "foobar" [case testUnknownModuleImportedWithinFunction2] def f(): @@ -360,7 +360,7 @@ x('') [out] main:2: error: Cannot find implementation or library stub for module named "foobar" main:2: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports -main:4: error: Too many arguments for "x" +main:4: error: Too many positional arguments for "x" [case testRelativeImports] import typing @@ -2964,7 +2964,7 @@ reveal_type(Some.name) # N: Revealed type is "builtins.str" from m1 import C from m1 import D # E: Module "m1" has no attribute "D" C() -C(1) # E: Too many arguments for "C" +C(1) # E: Too many positional arguments for "C" [file m1.pyi] from m2 import * [file m2.pyi] diff --git a/test-data/unit/check-namedtuple.test b/test-data/unit/check-namedtuple.test index a24a3964692f..fc9938561081 100644 --- a/test-data/unit/check-namedtuple.test +++ b/test-data/unit/check-namedtuple.test @@ -93,7 +93,7 @@ x = X(1, 'x') x.x x.z # E: "X" has no attribute "z" x = X(1) # E: Missing positional argument "y" in call to "X" -x = X(1, 2, 3) # E: Too many arguments for "X" +x = X(1, 2, 3) # E: Too many positional arguments for "X" [builtins fixtures/tuple.pyi] [case testCreateNamedTupleWithKeywordArguments] @@ -141,7 +141,7 @@ X = namedtuple('X', ['x', 'y'], defaults=(1,)) X() # E: Missing positional argument "x" in call to "X" X(0) # ok X(0, 1) # ok -X(0, 1, 2) # E: Too many arguments for "X" +X(0, 1, 2) # E: Too many positional arguments for "X" Y = namedtuple('Y', ['x', 'y'], defaults=(1, 2, 3)) # E: Too many defaults given in call to "namedtuple()" Z = namedtuple('Z', ['x', 'y'], defaults='not a tuple') # E: List or tuple literal expected as the defaults argument to namedtuple() # E: Argument "defaults" to "namedtuple" has incompatible type "str"; expected "Iterable[Any] | None" diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index caef59efaf99..4007f05de998 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -1112,9 +1112,11 @@ class C(B): ... class B: ... [case testNewAnalyzerIncompleteFixture] -from typing import Tuple +from typing import Set # E: Module "typing" has no attribute "Set" \ + # N: Maybe your test fixture does not define "builtins.set"? \ + # N: Consider adding [builtins fixtures/set.pyi] to your test description -x: Tuple[int] # E: Name "tuple" is not defined +x: Set[int] [builtins fixtures/complex.pyi] [case testNewAnalyzerMetaclass1] diff --git a/test-data/unit/check-newtype.test b/test-data/unit/check-newtype.test index 1e2775b3aaa6..f9b679467e4f 100644 --- a/test-data/unit/check-newtype.test +++ b/test-data/unit/check-newtype.test @@ -37,7 +37,7 @@ tcp_packet = TcpPacketId(packet) tcp_packet = TcpPacketId(127, 0) [out] -main:12: error: Too many arguments for "TcpPacketId" +main:12: error: Too many positional arguments for "TcpPacketId" main:12: error: Argument 1 to "TcpPacketId" has incompatible type "int"; expected "PacketId" [case testNewTypeWithTuples] diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 68eccb46ab21..1b06aa99ac02 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -1258,16 +1258,17 @@ from typing import overload def f(x: int, y: str) -> int: pass @overload def f(*x: str) -> str: pass -f(*(1,))() # E: No overload variant of "f" matches argument type "tuple[int]" \ - # N: Possible overload variants: \ - # N: def f(x: int, y: str) -> int \ - # N: def f(*x: str) -> str + +# second overload is plausible. +f(*(1,))() # E: "str" not callable \ + # E: Argument 1 to "f" has incompatible type "*tuple[int]"; expected "str" +# selected second overload f(*('',))() # E: "str" not callable +# selected first overload f(*(1, ''))() # E: "int" not callable -f(*(1, '', 1))() # E: No overload variant of "f" matches argument type "tuple[int, str, int]" \ - # N: Possible overload variants: \ - # N: def f(x: int, y: str) -> int \ - # N: def f(*x: str) -> str +# second overload is plausible. +f(*(1, '', 1))() # E: "str" not callable \ + # E: Argument 1 to "f" has incompatible type "*tuple[int, str, int]"; expected "str" [builtins fixtures/tuple.pyi] [case testPreferExactSignatureMatchInOverload] @@ -1906,6 +1907,58 @@ a: Any reveal_type(f(a)) # N: Revealed type is "def (*Any, **Any) -> Any" reveal_type(f(a)(a)) # N: Revealed type is "Any" +[case testOverloadWithOverlappingItemsAndAnyArgument17] +# https://github.com/python/mypy/issues/19860#issuecomment-3297377452 +from typing import TypeVar, Any, overload + +T = TypeVar("T") +X = TypeVar("X") +Y = TypeVar("Y") + +@overload +def broadcast(x: X, y: Y, /) -> tuple[X, Y]: ... +@overload +def broadcast(*args: T) -> tuple[T, ...]: ... +def broadcast(*args: object) -> tuple[object, ...]: + return args + +def test(x: int, y: Any) -> None: + a, b = broadcast(x, y) + # TODO: can we improve inference and get "int" for the first reveal_type? + reveal_type(a) # N: Revealed type is "Any" + reveal_type(b) # N: Revealed type is "Any" +[builtins fixtures/tuple.pyi] + +[case testOverloadWithOverlappingItemsAndAnyArgument18] +# https://github.com/python/mypy/issues/19860#issuecomment-3297377452 +# Same as testOverloadWithOverlappingItemsAndAnyArgument17 but with a +# subclass of tuple[T, ...] +from typing import TypeVar, Any, overload + +T = TypeVar("T") +X = TypeVar("X") +Y = TypeVar("Y") + +class MyTuple(tuple[T, ...]): ... + +@overload +def broadcast(x: X, y: Y, /) -> tuple[X, Y]: ... +@overload +def broadcast(*args: T) -> MyTuple[T]: ... +def broadcast(*args: object) -> tuple[object, ...]: + return args + +def test(x: int, y: Any) -> None: + a, b = broadcast(x, y) + # TODO: can we do better and get rid of these errors? + reveal_type(a) # E: Cannot determine type of "a" \ + # N: Revealed type is "Any" + reveal_type(b) # E: Cannot determine type of "b" \ + # N: Revealed type is "Any" +[builtins fixtures/tuple.pyi] + + + [case testOverloadOnOverloadWithType] from typing import Any, Type, TypeVar, overload from mod import MyInt diff --git a/test-data/unit/check-parameter-specification.test b/test-data/unit/check-parameter-specification.test index 41a6c5b33cb9..0d44889afbd9 100644 --- a/test-data/unit/check-parameter-specification.test +++ b/test-data/unit/check-parameter-specification.test @@ -2250,7 +2250,7 @@ def fn_posonly(x: int, /) -> None: ... def fn_posonly_args(x: int, /, y: str) -> None: ... run(fn) # E: Argument 1 to "run" has incompatible type "Callable[[], None]"; expected "Callable[[int], None]" -run(fn_args, 1, 'a') # E: Too many arguments for "run" \ +run(fn_args, 1, 'a') # E: Too many positional arguments for "run" \ # E: Argument 2 to "run" has incompatible type "int"; expected "str" run(fn_args, y='a') run(fn_args, 'a') @@ -2756,3 +2756,41 @@ reveal_type(Sneaky(f8, 1, y='').kwargs) # N: Revealed type is "builtins.dict[bu reveal_type(Sneaky(f9, 1, y=0).kwargs) # N: Revealed type is "TypedDict('builtins.dict', {'x'?: builtins.int, 'y': builtins.int, 'z'?: builtins.str})" reveal_type(Sneaky(f9, 1, y=0, z='').kwargs) # N: Revealed type is "TypedDict('builtins.dict', {'x'?: builtins.int, 'y': builtins.int, 'z'?: builtins.str})" [builtins fixtures/paramspec.pyi] + + +[case testParamSpecArgsAsTupleInteraction] +# https://github.com/python/mypy/issues/19663 +# flags: --warn-unreachable +from typing import Callable, TypeVar +from typing_extensions import ParamSpec, TypeVarTuple, Unpack + +P = ParamSpec("P") +Ts = TypeVarTuple("Ts") +TupleT = TypeVar("TupleT", bound=tuple) + +def as_tuple(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]: return args +def tuple_identity(t: TupleT) -> TupleT: return t +def tuple_identity2(t: tuple[Unpack[Ts]]) -> tuple[Unpack[Ts]]: return t + +def test_paramspec( + dummy: Callable[P, None], # ensure P is bound + /, + *args: P.args, + **kwargs: P.kwargs, +) -> None: + reveal_type( args ) # N: Revealed type is "P.args`-1" + reveal_type( (*args,) ) # N: Revealed type is "P.args`-1" + reveal_type(tuple_identity(args)) # N: Revealed type is "P.args`-1" + reveal_type(as_tuple(*args)) # N: Revealed type is "tuple[Unpack[P.args`-1]]" + # TODO: this one should possible be tuple[Any, ...] ? + reveal_type( tuple(args) ) # N: Revealed type is "builtins.tuple[builtins.object, ...]" + # TODO: this one should give the same result as tuple_identity above? + reveal_type(tuple_identity2(args)) # N: Revealed type is "builtins.tuple[Never, ...]" \ + # E: Argument 1 to "tuple_identity2" has incompatible type "P.args"; expected "tuple[Never, ...]" + + if isinstance(args, tuple): + pass + else: + reveal_type(args) # N: Revealed type is "P.args`-1" + +[builtins fixtures/paramspec.pyi] diff --git a/test-data/unit/check-plugin-attrs.test b/test-data/unit/check-plugin-attrs.test index 6cdee6fa578a..341670c0bdbe 100644 --- a/test-data/unit/check-plugin-attrs.test +++ b/test-data/unit/check-plugin-attrs.test @@ -14,7 +14,7 @@ reveal_type(A) # N: Revealed type is "def (a: Any, b: Any, c: Any =, d: Any =) A(1, [2]) A(1, [2], '3', 4) A(1, 2, 3, 4) -A(1, [2], '3', 4, 5) # E: Too many arguments for "A" +A(1, [2], '3', 4, 5) # E: Too many positional arguments for "A" [builtins fixtures/list.pyi] [case testAttrsAnnotated] @@ -32,7 +32,7 @@ reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.list[bu A(1, [2]) A(1, [2], '3', 4) A(1, 2, 3, 4) # E: Argument 2 to "A" has incompatible type "int"; expected "list[int]" # E: Argument 3 to "A" has incompatible type "int"; expected "str" -A(1, [2], '3', 4, 5) # E: Too many arguments for "A" +A(1, [2], '3', 4, 5) # E: Too many positional arguments for "A" [builtins fixtures/list.pyi] [case testAttrsTypeComments] @@ -50,7 +50,7 @@ reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.list[bu A(1, [2]) A(1, [2], '3', 4) A(1, 2, 3, 4) # E: Argument 2 to "A" has incompatible type "int"; expected "list[int]" # E: Argument 3 to "A" has incompatible type "int"; expected "str" -A(1, [2], '3', 4, 5) # E: Too many arguments for "A" +A(1, [2], '3', 4, 5) # E: Too many positional arguments for "A" [builtins fixtures/list.pyi] [case testAttrsAutoAttribs] @@ -68,7 +68,7 @@ reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.list[bu A(1, [2]) A(1, [2], '3', 4) A(1, 2, 3, 4) # E: Argument 2 to "A" has incompatible type "int"; expected "list[int]" # E: Argument 3 to "A" has incompatible type "int"; expected "str" -A(1, [2], '3', 4, 5) # E: Too many arguments for "A" +A(1, [2], '3', 4, 5) # E: Too many positional arguments for "A" [builtins fixtures/list.pyi] [case testAttrsUntypedNoUntypedDefs] @@ -121,7 +121,7 @@ reveal_type(A) # N: Revealed type is "def (a: Any, b: builtins.list[builtins.in A(1, [2]) A(1, [2], '3', 4) A(1, 2, 3, 4) # E: Argument 2 to "A" has incompatible type "int"; expected "list[int]" -A(1, [2], '3', 4, 5) # E: Too many arguments for "A" +A(1, [2], '3', 4, 5) # E: Too many positional arguments for "A" [builtins fixtures/list.pyi] [case testAttrsDefaultErrors] @@ -166,8 +166,8 @@ class A: _d: int = attrib(validator=None, default=18) reveal_type(A) # N: Revealed type is "def () -> __main__.A" A() -A(1, [2]) # E: Too many arguments for "A" -A(1, [2], '3', 4) # E: Too many arguments for "A" +A(1, [2]) # E: Too many positional arguments for "A" +A(1, [2], '3', 4) # E: Too many positional arguments for "A" [builtins fixtures/list.pyi] [case testAttrsInitAttribFalse] @@ -1280,7 +1280,8 @@ import attr class A: a = attr.ib(kw_only=True) A() # E: Missing named argument "a" for "A" -A(15) # E: Too many positional arguments for "A" +A(15) # E: Too many positional arguments for "A" \ + # E: Missing named argument "a" for "A" A(a=15) [builtins fixtures/plugin_attrs.pyi] @@ -1439,7 +1440,7 @@ def func() -> int: ... C() C(1) -C(1, 2) # E: Too many arguments for "C" +C(1, 2) # E: Too many positional arguments for "C" [builtins fixtures/bool.pyi] [case testTypeInAttrUndefinedFrozen] diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index b2f751bf3572..f6e7949425af 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -25,7 +25,7 @@ class C: x: P def fun(x: P) -> None: x.meth() - x.meth(x) # E: Too many arguments for "meth" of "P" + x.meth(x) # E: Too many positional arguments for "meth" of "P" x.bad # E: "P" has no attribute "bad" x = C() @@ -110,7 +110,7 @@ class D(B): x: P def fun(x: P) -> None: x.meth() - x.meth(x) # E: Too many arguments for "meth" of "P" + x.meth(x) # E: Too many positional arguments for "meth" of "P" x.bad # E: "P" has no attribute "bad" x = C() diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test index c32dec94a62d..4c6df92f68e7 100644 --- a/test-data/unit/check-python38.test +++ b/test-data/unit/check-python38.test @@ -149,7 +149,8 @@ f(arg="ERROR") # E: Unexpected keyword argument "arg" for "f" from typing import Any, Dict def f(p, /, p_or_kw, *, kw) -> None: ... # N: "f" defined here d = None # type: Dict[Any, Any] -f(0, 0, 0) # E: Too many positional arguments for "f" +f(0, 0, 0) # E: Too many positional arguments for "f" \ + # E: Missing named argument "kw" for "f" f(0, 0, kw=0) f(0, p_or_kw=0, kw=0) f(p=0, p_or_kw=0, kw=0) # E: Unexpected keyword argument "p" for "f" diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index 685b11c532cf..ed9ac86877eb 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -308,7 +308,7 @@ class A: def new(cls: Type[T], factory: Callable[[T], T]) -> T: reveal_type(cls) # N: Revealed type is "type[T`-1]" reveal_type(cls()) # N: Revealed type is "T`-1" - cls(2) # E: Too many arguments for "A" + cls(2) # E: Too many positional arguments for "A" return cls() class B(A): diff --git a/test-data/unit/check-serialize.test b/test-data/unit/check-serialize.test index 3be96bb53ee1..7fcd8f96e4ab 100644 --- a/test-data/unit/check-serialize.test +++ b/test-data/unit/check-serialize.test @@ -415,7 +415,7 @@ class A: def f() -> None: pass [builtins fixtures/staticmethod.pyi] [out2] -tmp/a.py:2: error: Too many arguments for "f" of "A" +tmp/a.py:2: error: Too many positional arguments for "f" of "A" [case testSerializeClassMethod] import a @@ -432,7 +432,7 @@ class A: def f(cls) -> None: pass [builtins fixtures/classmethod.pyi] [out2] -tmp/a.py:2: error: Too many arguments for "f" of "A" +tmp/a.py:2: error: Too many positional arguments for "f" of "A" [case testSerializeReadOnlyProperty] import a @@ -510,8 +510,8 @@ class B: class C(A, B): def h(self) -> int: pass [out2] -tmp/a.py:2: error: Too many arguments for "f" of "A" -tmp/a.py:3: error: Too many arguments for "g" of "B" +tmp/a.py:2: error: Too many positional arguments for "f" of "A" +tmp/a.py:3: error: Too many positional arguments for "g" of "B" tmp/a.py:4: note: Revealed type is "builtins.int" tmp/a.py:7: error: Incompatible types in assignment (expression has type "C", variable has type "int") @@ -548,7 +548,7 @@ class A(Tuple[int, str]): def f(self) -> None: pass [builtins fixtures/tuple.pyi] [out2] -tmp/a.py:3: error: Too many arguments for "f" of "A" +tmp/a.py:3: error: Too many positional arguments for "f" of "A" tmp/a.py:4: note: Revealed type is "tuple[builtins.int, builtins.str]" [case testSerializeVariableLengthTupleBaseClass] @@ -566,7 +566,7 @@ class A(Tuple[int, ...]): def f(self) -> None: pass [builtins fixtures/tuple.pyi] [out2] -tmp/a.py:3: error: Too many arguments for "f" of "A" +tmp/a.py:3: error: Too many positional arguments for "f" of "A" tmp/a.py:4: note: Revealed type is "tuple[builtins.int, builtins.int]" [case testSerializePlainTupleBaseClass] @@ -584,7 +584,7 @@ class A(tuple): def f(self) -> None: pass [builtins fixtures/tuple.pyi] [out2] -tmp/a.py:3: error: Too many arguments for "f" of "A" +tmp/a.py:3: error: Too many positional arguments for "f" of "A" tmp/a.py:4: note: Revealed type is "tuple[Any, Any]" [case testSerializeNamedTupleBaseClass] @@ -603,7 +603,7 @@ class A(NamedTuple('N', [('x', int), ('y', str)])): def f(self) -> None: pass [builtins fixtures/tuple.pyi] [out2] -tmp/a.py:3: error: Too many arguments for "f" of "A" +tmp/a.py:3: error: Too many positional arguments for "f" of "A" tmp/a.py:4: note: Revealed type is "tuple[builtins.int, builtins.str]" tmp/a.py:5: note: Revealed type is "tuple[builtins.int, builtins.str]" @@ -622,7 +622,7 @@ class B(A): def f(self) -> None: pass [builtins fixtures/tuple.pyi] [out2] -tmp/a.py:2: error: Too many arguments for "f" of "B" +tmp/a.py:2: error: Too many positional arguments for "f" of "B" tmp/a.py:3: note: Revealed type is "Any" [case testSerializeIndirectAnyBaseClass] @@ -643,8 +643,8 @@ class C(B): def g(self) -> None: pass [builtins fixtures/tuple.pyi] [out2] -tmp/a.py:2: error: Too many arguments for "f" of "B" -tmp/a.py:3: error: Too many arguments for "g" of "C" +tmp/a.py:2: error: Too many positional arguments for "f" of "B" +tmp/a.py:3: error: Too many positional arguments for "g" of "C" tmp/a.py:4: note: Revealed type is "Any" [case testSerializeNestedClass] @@ -667,10 +667,10 @@ b: A.B c: A.B.C [builtins fixtures/tuple.pyi] [out2] -tmp/a.py:2: error: Too many arguments for "f" of "B" -tmp/a.py:3: error: Too many arguments for "g" of "C" -tmp/a.py:4: error: Too many arguments for "f" of "B" -tmp/a.py:5: error: Too many arguments for "g" of "C" +tmp/a.py:2: error: Too many positional arguments for "f" of "B" +tmp/a.py:3: error: Too many positional arguments for "g" of "C" +tmp/a.py:4: error: Too many positional arguments for "f" of "B" +tmp/a.py:5: error: Too many positional arguments for "g" of "C" [case testSerializeCallableVsTypeObjectDistinction] import a @@ -1129,10 +1129,10 @@ from c import A [file c.py] class A: pass [out1] -main:2: error: Too many arguments for "A" +main:2: error: Too many positional arguments for "A" main:3: note: Revealed type is "c.A" [out2] -main:2: error: Too many arguments for "A" +main:2: error: Too many positional arguments for "A" main:3: note: Revealed type is "c.A" [case testSerializeFromImportedClassAs] @@ -1144,10 +1144,10 @@ from c import A as B [file c.py] class A: pass [out1] -main:2: error: Too many arguments for "A" +main:2: error: Too many positional arguments for "A" main:3: note: Revealed type is "c.A" [out2] -main:2: error: Too many arguments for "A" +main:2: error: Too many positional arguments for "A" main:3: note: Revealed type is "c.A" [case testSerializeFromImportedModule] @@ -1261,7 +1261,7 @@ from c import * def f() -> None: pass class A: pass [out2] -tmp/a.py:2: error: Too many arguments for "f" +tmp/a.py:2: error: Too many positional arguments for "f" tmp/a.py:4: note: Revealed type is "c.A" [case testSerializeRelativeImport] @@ -1273,9 +1273,9 @@ from .d import f [file b/d.py] def f() -> None: pass [out1] -main:2: error: Too many arguments for "f" +main:2: error: Too many positional arguments for "f" [out2] -main:2: error: Too many arguments for "f" +main:2: error: Too many positional arguments for "f" [case testSerializeDummyType] # flags: --no-strict-optional diff --git a/test-data/unit/check-statements.test b/test-data/unit/check-statements.test index 880df82671d4..c76985756d61 100644 --- a/test-data/unit/check-statements.test +++ b/test-data/unit/check-statements.test @@ -655,7 +655,7 @@ import typing try: pass except BaseException: pass else: - object(None) # E: Too many arguments for "object" + object(None) # E: Too many positional arguments for "object" [builtins fixtures/exception.pyi] [case testRedefinedFunctionInTryWithElse] @@ -1411,14 +1411,14 @@ class A: def __enter__(self) -> None: pass def __exit__(self, x, y, z) -> None: pass with A(): - object(A) # E: Too many arguments for "object" + object(A) # E: Too many positional arguments for "object" [case testWithStmtAndInvalidExit] import typing class A: def __enter__(self) -> None: pass def __exit__(self, x, y) -> None: pass -with A(): # E: Too many arguments for "__exit__" of "A" +with A(): # E: Too many positional arguments for "__exit__" of "A" pass [case testWithStmtAndMissingExit] diff --git a/test-data/unit/check-super.test b/test-data/unit/check-super.test index 8816322a270a..fe5bac148e9b 100644 --- a/test-data/unit/check-super.test +++ b/test-data/unit/check-super.test @@ -79,7 +79,7 @@ class C(A, B): def f(self) -> None: super().f() super().g(1) - super().f(1) # E: Too many arguments for "f" of "A" + super().f(1) # E: Too many positional arguments for "f" of "A" super().g() # E: Missing positional argument "x" in call to "g" of "B" super().not_there() # E: "not_there" undefined in superclass [out] @@ -92,7 +92,7 @@ class A: class B(A): def __new__(cls, x: int, y: str = '') -> 'B': super().__new__(cls, 1) - super().__new__(cls, 1, '') # E: Too many arguments for "__new__" of "A" + super().__new__(cls, 1, '') # E: Too many positional arguments for "__new__" of "A" return cls(1) B('') # E: Argument 1 to "B" has incompatible type "str"; expected "int" B(1) diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index 3dccb6ce9834..e39ae5b7beec 100644 --- a/test-data/unit/check-tuples.test +++ b/test-data/unit/check-tuples.test @@ -1117,7 +1117,7 @@ reveal_type(b) # N: Revealed type is "tuple[builtins.int, builtins.int, builtin [case testTupleWithStarExpr2] a = [1] b = (0, *a) -reveal_type(b) # N: Revealed type is "builtins.tuple[builtins.int, ...]" +reveal_type(b) # N: Revealed type is "tuple[builtins.int, Unpack[builtins.tuple[builtins.int, ...]]]" [builtins fixtures/tuple.pyi] [case testTupleWithStarExpr2Precise] @@ -1130,9 +1130,9 @@ reveal_type(b) # N: Revealed type is "tuple[builtins.int, Unpack[builtins.tuple [case testTupleWithStarExpr3] a = [''] b = (0, *a) -reveal_type(b) # N: Revealed type is "builtins.tuple[builtins.object, ...]" +reveal_type(b) # N: Revealed type is "tuple[builtins.int, Unpack[builtins.tuple[builtins.str, ...]]]" c = (*a, '') -reveal_type(c) # N: Revealed type is "builtins.tuple[builtins.str, ...]" +reveal_type(c) # N: Revealed type is "tuple[Unpack[builtins.tuple[builtins.str, ...]], builtins.str]" [builtins fixtures/tuple.pyi] [case testTupleWithStarExpr3Precise] @@ -1824,3 +1824,166 @@ class tuple_aa_subclass(Tuple[A, A]): ... inst_tuple_aa_subclass: tuple_aa_subclass = tuple_aa_subclass((A(), A()))[:] # E: Incompatible types in assignment (expression has type "tuple[A, A]", variable has type "tuple_aa_subclass") [builtins fixtures/tuple.pyi] + + +[case testLeftAndRightVariadicCoupled] +# test solving cases with infinitely many solutions. +from typing import TypeVar, Unpack + +class A: ... +class A1: ... +class A2: ... +class A3: ... +class A4: ... + +T = TypeVar("T") + +def left_variadic( + *args: Unpack[tuple[Unpack[tuple[T, ...]], T, T, T]], +) -> T: ... + +def right_variadic( + *args: Unpack[tuple[T, T, T, Unpack[tuple[T, ...]]]], +) -> T: ... + +def test_right_variadic_param_with_left_variadic_argument( + l0: tuple[Unpack[tuple[A, ...]]], + l1: tuple[Unpack[tuple[A, ...]], A1], + l2: tuple[Unpack[tuple[A, ...]], A1, A2], + l3: tuple[Unpack[tuple[A, ...]], A1, A2, A3], + l4: tuple[Unpack[tuple[A, ...]], A1, A2, A3, A4], +) -> None: + # tuple[T, T, T, *tuple[T, ...]] -> T + reveal_type( right_variadic(*l0) ) # N: Revealed type is "__main__.A" + reveal_type( right_variadic(*l1) ) # N: Revealed type is "builtins.object" + reveal_type( right_variadic(*l2) ) # N: Revealed type is "builtins.object" + reveal_type( right_variadic(*l3) ) # N: Revealed type is "builtins.object" + reveal_type( right_variadic(*l4) ) # N: Revealed type is "builtins.object" + # NOTE: when using union instead of join this should be Union[A, A1, A2, A3, A4] + +def test_right_variadic_param_with_right_variadic_argument( + r0: tuple[Unpack[tuple[A, ...]]], + r1: tuple[A1, Unpack[tuple[A, ...]]], + r2: tuple[A1, A2, Unpack[tuple[A, ...]]], + r3: tuple[A1, A2, A3, Unpack[tuple[A, ...]]], + r4: tuple[A1, A2, A3, A4, Unpack[tuple[A, ...]]], +) -> None: + # tuple[T, T, T, *tuple[T, ...]] -> T + reveal_type( right_variadic(*r0) ) # N: Revealed type is "__main__.A" + reveal_type( right_variadic(*r1) ) # N: Revealed type is "builtins.object" + reveal_type( right_variadic(*r2) ) # N: Revealed type is "builtins.object" + reveal_type( right_variadic(*r3) ) # N: Revealed type is "builtins.object" + reveal_type( right_variadic(*r4) ) # N: Revealed type is "builtins.object" + # NOTE: when using union instead of join this should be Union[A, A1, A2, A3, A4] + +def test_left_variadic_param_with_right_variadic_argument( + r0: tuple[Unpack[tuple[A, ...]]], + r1: tuple[A1, Unpack[tuple[A, ...]]], + r2: tuple[A1, A2, Unpack[tuple[A, ...]]], + r3: tuple[A1, A2, A3, Unpack[tuple[A, ...]]], + r4: tuple[A1, A2, A3, A4, Unpack[tuple[A, ...]]], +)-> None: + # tuple[*tuple[T, ...], T, T, T] -> T + reveal_type( left_variadic(*r0) ) # N: Revealed type is "__main__.A" + reveal_type( left_variadic(*r1) ) # N: Revealed type is "builtins.object" + reveal_type( left_variadic(*r2) ) # N: Revealed type is "builtins.object" + reveal_type( left_variadic(*r3) ) # N: Revealed type is "builtins.object" + reveal_type( left_variadic(*r4) ) # N: Revealed type is "builtins.object" + # NOTE: when using union instead of join this should be Union[A, A1, A2, A3, A4] + +def test_left_variadic_param_with_left_variadic_argument( + l0: tuple[Unpack[tuple[A, ...]]], + l1: tuple[Unpack[tuple[A, ...]], A1], + l2: tuple[Unpack[tuple[A, ...]], A1, A2], + l3: tuple[Unpack[tuple[A, ...]], A1, A2, A3], + l4: tuple[Unpack[tuple[A, ...]], A1, A2, A3, A4], +)-> None: + # tuple[*tuple[T, ...], T, T, T] -> T + reveal_type( left_variadic(*l0) ) # N: Revealed type is "__main__.A" + reveal_type( left_variadic(*l1) ) # N: Revealed type is "builtins.object" + reveal_type( left_variadic(*l2) ) # N: Revealed type is "builtins.object" + reveal_type( left_variadic(*l3) ) # N: Revealed type is "builtins.object" + reveal_type( left_variadic(*l4) ) # N: Revealed type is "builtins.object" + # NOTE: when using union instead of join this should be Union[A, A1, A2, A3, A4] +[builtins fixtures/tuple.pyi] + + +[case testLeftAndRightVariadicTypeVarTuple] +# test solving cases with infinitely many solutions. +from typing import TypeVar, TypeVarTuple, Unpack + +class A: ... +class A1: ... +class A2: ... +class A3: ... +class A4: ... + +T1 = TypeVar("T1") +T2 = TypeVar("T2") +T3 = TypeVar("T3") +Ts = TypeVarTuple("Ts") + +def left_variadic( + *args: Unpack[tuple[Unpack[Ts], T1, T2, T3]], +) -> tuple[Unpack[Ts], T1, T2, T3]: ... + +def right_variadic( + *args: Unpack[tuple[T1, T2, T3, Unpack[Ts]]], +) -> tuple[T1, T2, T3, Unpack[Ts]]: ... + +def test_right_variadic_param_with_left_variadic_argument( + l0: tuple[Unpack[tuple[A, ...]]], + l1: tuple[Unpack[tuple[A, ...]], A1], + l2: tuple[Unpack[tuple[A, ...]], A1, A2], + l3: tuple[Unpack[tuple[A, ...]], A1, A2, A3], + l4: tuple[Unpack[tuple[A, ...]], A1, A2, A3, A4], +) -> None: + # tuple[T1, T2, T3, *Ts] -> tuple[T1, T2, T3, *Ts] + reveal_type( right_variadic(*l0) ) # N: Revealed type is "tuple[__main__.A, __main__.A, __main__.A, Unpack[builtins.tuple[__main__.A, ...]]]" + reveal_type( right_variadic(*l1) ) # N: Revealed type is "tuple[__main__.A, __main__.A, __main__.A, Unpack[builtins.tuple[__main__.A, ...]], __main__.A1]" + reveal_type( right_variadic(*l2) ) # N: Revealed type is "tuple[__main__.A, __main__.A, __main__.A, Unpack[builtins.tuple[__main__.A, ...]], __main__.A1, __main__.A2]" + reveal_type( right_variadic(*l3) ) # N: Revealed type is "tuple[__main__.A, __main__.A, __main__.A, Unpack[builtins.tuple[__main__.A, ...]], __main__.A1, __main__.A2, __main__.A3]" + reveal_type( right_variadic(*l4) ) # N: Revealed type is "tuple[__main__.A, __main__.A, __main__.A, Unpack[builtins.tuple[__main__.A, ...]], __main__.A1, __main__.A2, __main__.A3, __main__.A4]" + +def test_right_variadic_param_with_right_variadic_argument( + r0: tuple[Unpack[tuple[A, ...]]], + r1: tuple[A1, Unpack[tuple[A, ...]]], + r2: tuple[A1, A2, Unpack[tuple[A, ...]]], + r3: tuple[A1, A2, A3, Unpack[tuple[A, ...]]], + r4: tuple[A1, A2, A3, A4, Unpack[tuple[A, ...]]], +) -> None: + # tuple[T1, T2, T3, *Ts] -> tuple[T1, T2, T3, *Ts] + reveal_type( right_variadic(*r0) ) # N: Revealed type is "tuple[__main__.A, __main__.A, __main__.A, Unpack[builtins.tuple[__main__.A, ...]]]" + reveal_type( right_variadic(*r1) ) # N: Revealed type is "tuple[__main__.A1, __main__.A, __main__.A, Unpack[builtins.tuple[__main__.A, ...]]]" + reveal_type( right_variadic(*r2) ) # N: Revealed type is "tuple[__main__.A1, __main__.A2, __main__.A, Unpack[builtins.tuple[__main__.A, ...]]]" + reveal_type( right_variadic(*r3) ) # N: Revealed type is "tuple[__main__.A1, __main__.A2, __main__.A3, Unpack[builtins.tuple[__main__.A, ...]]]" + reveal_type( right_variadic(*r4) ) # N: Revealed type is "tuple[__main__.A1, __main__.A2, __main__.A3, __main__.A4, Unpack[builtins.tuple[__main__.A, ...]]]" + +def test_left_variadic_param_with_right_variadic_argument( + r0: tuple[Unpack[tuple[A, ...]]], + r1: tuple[A1, Unpack[tuple[A, ...]]], + r2: tuple[A1, A2, Unpack[tuple[A, ...]]], + r3: tuple[A1, A2, A3, Unpack[tuple[A, ...]]], + r4: tuple[A1, A2, A3, A4, Unpack[tuple[A, ...]]], +)-> None: + # tuple[*Ts, T1, T2, T3] -> tuple[*Ts, T1, T2, T3] + reveal_type( left_variadic(*r0) ) # N: Revealed type is "tuple[Unpack[builtins.tuple[__main__.A, ...]], __main__.A, __main__.A, __main__.A]" + reveal_type( left_variadic(*r1) ) # N: Revealed type is "tuple[__main__.A1, Unpack[builtins.tuple[__main__.A, ...]], __main__.A, __main__.A, __main__.A]" + reveal_type( left_variadic(*r2) ) # N: Revealed type is "tuple[__main__.A1, __main__.A2, Unpack[builtins.tuple[__main__.A, ...]], __main__.A, __main__.A, __main__.A]" + reveal_type( left_variadic(*r3) ) # N: Revealed type is "tuple[__main__.A1, __main__.A2, __main__.A3, Unpack[builtins.tuple[__main__.A, ...]], __main__.A, __main__.A, __main__.A]" + reveal_type( left_variadic(*r4) ) # N: Revealed type is "tuple[__main__.A1, __main__.A2, __main__.A3, __main__.A4, Unpack[builtins.tuple[__main__.A, ...]], __main__.A, __main__.A, __main__.A]" + +def test_left_variadic_param_with_left_variadic_argument( + l0: tuple[Unpack[tuple[A, ...]]], + l1: tuple[Unpack[tuple[A, ...]], A1], + l2: tuple[Unpack[tuple[A, ...]], A1, A2], + l3: tuple[Unpack[tuple[A, ...]], A1, A2, A3], + l4: tuple[Unpack[tuple[A, ...]], A1, A2, A3, A4], +)-> None: + # tuple[*Ts, T1, T2, T3] -> tuple[*Ts, T1, T2, T3] + reveal_type( left_variadic(*l0) ) # N: Revealed type is "tuple[Unpack[builtins.tuple[__main__.A, ...]], __main__.A, __main__.A, __main__.A]" + reveal_type( left_variadic(*l1) ) # N: Revealed type is "tuple[Unpack[builtins.tuple[__main__.A, ...]], __main__.A, __main__.A, __main__.A1]" + reveal_type( left_variadic(*l2) ) # N: Revealed type is "tuple[Unpack[builtins.tuple[__main__.A, ...]], __main__.A, __main__.A1, __main__.A2]" + reveal_type( left_variadic(*l3) ) # N: Revealed type is "tuple[Unpack[builtins.tuple[__main__.A, ...]], __main__.A1, __main__.A2, __main__.A3]" + reveal_type( left_variadic(*l4) ) # N: Revealed type is "tuple[Unpack[builtins.tuple[__main__.A, ...]], __main__.A1, __main__.A2, __main__.A3, __main__.A4]" +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-type-aliases.test b/test-data/unit/check-type-aliases.test index 0d2e6b5f0c9d..7dbb683261f4 100644 --- a/test-data/unit/check-type-aliases.test +++ b/test-data/unit/check-type-aliases.test @@ -704,7 +704,7 @@ class MyClass: ... from typing_extensions import TypeAlias x: TypeAlias = list(int) # E: Invalid type alias: expression is not a valid type \ - # E: Too many arguments for "list" + # E: Too many positional arguments for "list" a: x [builtins fixtures/tuple.pyi] @@ -1104,8 +1104,9 @@ t3: T3 reveal_type(t3) # N: Revealed type is "Any" T4 = TypeAliasType("T4") # E: Missing positional argument "value" in call to "TypeAliasType" -T5 = TypeAliasType("T5", int, str) # E: Too many positional arguments for "TypeAliasType" \ - # E: Argument 3 to "TypeAliasType" has incompatible type "type[str]"; expected "tuple[TypeVar? | ParamSpec? | TypeVarTuple?, ...]" +T5 = TypeAliasType("T5", int, str) # E: Too many positional arguments for "TypeAliasType" +T6 = TypeAliasType("T6", int, type_params=str) # E: Tuple literal expected as the type_params argument to TypeAliasType +T7 = TypeAliasType("T7", int, type_params=(str,)) # E: Free type variable expected in type_params argument to TypeAliasType [builtins fixtures/tuple.pyi] [typing fixtures/typing-full.pyi] diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index 29e3a7efbd8c..8a1fc69a9e25 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -3767,7 +3767,8 @@ class Point(TypedDict, total=False): def func(cls: Type[Point]) -> None: reveal_type(cls) # N: Revealed type is "type[TypedDict('__main__.Point', {'x': builtins.int, 'y'?: builtins.int})]" cls(x=1, y=2) - cls(1, 2) # E: Too many positional arguments + cls(1, 2) # E: Too many positional arguments \ + # E: Missing named argument "x" cls(x=1) cls(y=2) # E: Missing named argument "x" cls(x=1, y=2, error="") # E: Unexpected keyword argument "error" diff --git a/test-data/unit/check-typeform.test b/test-data/unit/check-typeform.test index 6d59cb008111..a9536562f08f 100644 --- a/test-data/unit/check-typeform.test +++ b/test-data/unit/check-typeform.test @@ -792,6 +792,7 @@ print(typx.__class__) # OK print(typx.__hash__()) # OK obj: object = typx [file builtins.py] +from typing import Mapping class object: def __init__(self) -> None: pass __class__: None diff --git a/test-data/unit/check-typevar-tuple.test b/test-data/unit/check-typevar-tuple.test index f646fd18e65f..45aca9a449e0 100644 --- a/test-data/unit/check-typevar-tuple.test +++ b/test-data/unit/check-typevar-tuple.test @@ -57,7 +57,7 @@ f_args3: Tuple[int, str, bool] reveal_type(f(f_args)) # N: Revealed type is "tuple[builtins.str, builtins.str]" reveal_type(f(f_args2)) # N: Revealed type is "tuple[builtins.str]" reveal_type(f(f_args3)) # N: Revealed type is "tuple[builtins.str, builtins.str, builtins.bool]" -f(empty) # E: Argument 1 to "f" has incompatible type "tuple[()]"; expected "tuple[int]" +f(empty) # E: Argument 1 to "f" has incompatible type "tuple[()]"; expected "tuple[int, Unpack[tuple[Never, ...]]]" f(bad_args) # E: Argument 1 to "f" has incompatible type "tuple[str, str]"; expected "tuple[int, str]" # The reason for error in subtle: actual can be empty, formal cannot. @@ -395,18 +395,20 @@ from typing import Tuple from typing_extensions import TypeVarTuple, Unpack Ts = TypeVarTuple("Ts") +Vs = TypeVarTuple("Vs") def args_to_tuple(*args: Unpack[Ts]) -> Tuple[Unpack[Ts]]: - with_prefix_suffix(*args) # E: Too few arguments for "with_prefix_suffix" \ - # E: Argument 1 to "with_prefix_suffix" has incompatible type "*tuple[Unpack[Ts]]"; expected "bool" + x = with_prefix_suffix(*args) # E: Argument 1 to "with_prefix_suffix" has incompatible type "*tuple[Unpack[Ts]]"; expected "Unpack[tuple[Unpack[tuple[Any, ...]], int]]" + reveal_type(x) # N: Revealed type is "tuple[builtins.bool, builtins.str, Unpack[builtins.tuple[Any, ...]], builtins.int]" new_args = (True, "foo", *args, 5) - with_prefix_suffix(*new_args) + y = with_prefix_suffix(*new_args) + reveal_type(y) # N: Revealed type is "tuple[builtins.bool, builtins.str, Unpack[Ts`-1], builtins.int]" return args -def with_prefix_suffix(*args: Unpack[Tuple[bool, str, Unpack[Ts], int]]) -> Tuple[bool, str, Unpack[Ts], int]: - reveal_type(args) # N: Revealed type is "tuple[builtins.bool, builtins.str, Unpack[Ts`-1], builtins.int]" - reveal_type(args_to_tuple(*args)) # N: Revealed type is "tuple[builtins.bool, builtins.str, Unpack[Ts`-1], builtins.int]" - reveal_type(args_to_tuple(1, *args, 'a')) # N: Revealed type is "tuple[Literal[1]?, builtins.bool, builtins.str, Unpack[Ts`-1], builtins.int, Literal['a']?]" +def with_prefix_suffix(*args: Unpack[Tuple[bool, str, Unpack[Vs], int]]) -> Tuple[bool, str, Unpack[Vs], int]: + reveal_type(args) # N: Revealed type is "tuple[builtins.bool, builtins.str, Unpack[Vs`-1], builtins.int]" + reveal_type(args_to_tuple(*args)) # N: Revealed type is "tuple[builtins.bool, builtins.str, Unpack[Vs`-1], builtins.int]" + reveal_type(args_to_tuple(1, *args, 'a')) # N: Revealed type is "tuple[Literal[1]?, builtins.bool, builtins.str, Unpack[Vs`-1], builtins.int, Literal['a']?]" return args reveal_type(with_prefix_suffix(True, "bar", "foo", 5)) # N: Revealed type is "tuple[builtins.bool, builtins.str, Literal['foo']?, builtins.int]" @@ -438,7 +440,7 @@ def foo(*args: Unpack[Tuple[int, str]]) -> None: foo(0, "foo") foo(0, 1) # E: Argument 2 to "foo" has incompatible type "int"; expected "str" foo("foo", "bar") # E: Argument 1 to "foo" has incompatible type "str"; expected "int" -foo(0, "foo", 1) # E: Too many arguments for "foo" +foo(0, "foo", 1) # E: Too many positional arguments for "foo" foo(0) # E: Too few arguments for "foo" foo() # E: Too few arguments for "foo" foo(*(0, "foo")) @@ -961,7 +963,7 @@ Ts = TypeVarTuple("Ts") def f(x: Ts) -> Ts: # E: TypeVarTuple "Ts" is only valid with an unpack return x -v = f(1, 2, "A") # E: Too many arguments for "f" +v = f(1, 2, "A") # E: Too many positional arguments for "f" reveal_type(v) # N: Revealed type is "Any" [builtins fixtures/tuple.pyi] @@ -1329,8 +1331,8 @@ f3(tl) f3(tr) f4(t1) -f4(t2) # E: Argument 1 to "f4" has incompatible type "tuple[int, Unpack[tuple[int, ...]]]"; expected "tuple[float, Unpack[tuple[float, ...]], float]" -f4(t3) # E: Argument 1 to "f4" has incompatible type "tuple[Unpack[tuple[int, ...]], int]"; expected "tuple[float, Unpack[tuple[float, ...]], float]" +f4(t2) +f4(t3) f4(t4) f4(t5) # E: Argument 1 to "f4" has incompatible type "tuple[int, ...]"; expected "tuple[float, Unpack[tuple[float, ...]], float]" @@ -1543,13 +1545,13 @@ def foo(arg: Tuple[int, Unpack[Ts], str]) -> None: reveal_type(z1) # N: Revealed type is "Any" x2, *y2, z2 = arg reveal_type(x2) # N: Revealed type is "builtins.int" - reveal_type(y2) # N: Revealed type is "builtins.list[builtins.object]" + reveal_type(y2) # N: Revealed type is "builtins.list[Any]" reveal_type(z2) # N: Revealed type is "builtins.str" x3, *y3 = arg reveal_type(x3) # N: Revealed type is "builtins.int" - reveal_type(y3) # N: Revealed type is "builtins.list[builtins.object]" + reveal_type(y3) # N: Revealed type is "builtins.list[Any]" *y4, z4 = arg - reveal_type(y4) # N: Revealed type is "builtins.list[builtins.object]" + reveal_type(y4) # N: Revealed type is "builtins.list[Any]" reveal_type(z4) # N: Revealed type is "builtins.str" x5, xx5, *y5, z5, zz5 = arg # E: Too many assignment targets for variadic unpack reveal_type(x5) # N: Revealed type is "Any" @@ -1652,7 +1654,7 @@ def foo(arg: Tuple[int, Unpack[Ts], str]) -> None: y = 1, *arg, 2 reveal_type(y) # N: Revealed type is "tuple[builtins.int, builtins.int, Unpack[Ts`-1], builtins.str, builtins.int]" z = (*arg, *arg) - reveal_type(z) # N: Revealed type is "builtins.tuple[builtins.object, ...]" + reveal_type(z) # N: Revealed type is "tuple[builtins.int, Unpack[builtins.tuple[Any | builtins.str | builtins.int, ...]], builtins.str]" [builtins fixtures/tuple.pyi] [case testPackingVariadicTuplesHomogeneous] @@ -1675,7 +1677,7 @@ reveal_type(x2) # N: Revealed type is "tuple[builtins.int, Unpack[builtins.tupl y2 = 1, *b, 2 reveal_type(y2) # N: Revealed type is "tuple[builtins.int, builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str, builtins.int]" z2 = (*b, *b) -reveal_type(z2) # N: Revealed type is "builtins.tuple[builtins.object, ...]" +reveal_type(z2) # N: Revealed type is "tuple[builtins.int, Unpack[builtins.tuple[builtins.float | builtins.str | builtins.int, ...]], builtins.str]" [builtins fixtures/tuple.pyi] [case testVariadicTupleInListSetExpr] @@ -1688,8 +1690,8 @@ reveal_type({1, *vt}) # N: Revealed type is "builtins.set[builtins.float]" Ts = TypeVarTuple("Ts") def foo(arg: Tuple[int, Unpack[Ts], str]) -> None: - reveal_type([1, *arg]) # N: Revealed type is "builtins.list[builtins.object]" - reveal_type({1, *arg}) # N: Revealed type is "builtins.set[builtins.object]" + reveal_type([1, *arg]) # N: Revealed type is "builtins.list[Any]" + reveal_type({1, *arg}) # N: Revealed type is "builtins.set[Any]" [builtins fixtures/isinstancelist.pyi] [case testVariadicTupleInTupleContext] @@ -1723,7 +1725,7 @@ Ts = TypeVarTuple("Ts") def foo(arg: Tuple[int, Unpack[Ts], str]) -> None: reveal_type(arg + (1, 2)) # N: Revealed type is "tuple[builtins.int, Unpack[Ts`-1], builtins.str, Literal[1]?, Literal[2]?]" reveal_type((1, 2) + arg) # N: Revealed type is "tuple[Literal[1]?, Literal[2]?, builtins.int, Unpack[Ts`-1], builtins.str]" - reveal_type(arg + arg) # N: Revealed type is "builtins.tuple[builtins.object, ...]" + reveal_type(arg + arg) # N: Revealed type is "builtins.tuple[builtins.int | Any | builtins.str, ...]" [builtins fixtures/tuple.pyi] [case testTypeVarTupleAnyOverload] @@ -1830,7 +1832,7 @@ A1 = Tuple[int, Unpack[Tuple[Int, ...]]] B1 = Tuple[Unpack[Tuple[Int, ...]], int] @overload -def f1(arg: A1) -> int: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +def f1(arg: A1) -> int: ... @overload def f1(arg: B1) -> str: ... def f1(arg: Union[A1, B1]) -> Union[int, str]: @@ -2739,3 +2741,21 @@ def foo() -> str: # this is a false positive, but it no longer crashes call(run, foo, some_kwarg="a") # E: Argument 1 to "call" has incompatible type "def [Ts`-1, T] run(func: def (*Unpack[Ts]) -> T, *args: Unpack[Ts], some_kwarg: str = ...) -> T"; expected "Callable[[Callable[[], str], str], str]" [builtins fixtures/tuple.pyi] + +[case testParamSpecWithVarArgsTypeVarTuple] +# https://github.com/python/mypy/issues/19855 +from typing import Callable, TypeVar +from typing_extensions import TypeVarTuple, ParamSpec, Unpack + +T = TypeVar("T") +P = ParamSpec("P") +Us = TypeVarTuple("Us") +Ts = TypeVarTuple("Ts") + +def run(fn: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T: ... +def coroutine_or_error(async_fn: Callable[[Unpack[Us]], int], *args: Unpack[Us]) -> int: ... + +def test(async_fn: Callable[[Unpack[Ts]], int], args: tuple[Unpack[Ts]]) -> None: + r = run(coroutine_or_error, async_fn, *args) + reveal_type(r) # N: Revealed type is "builtins.int" +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-varargs.test b/test-data/unit/check-varargs.test index a400c88cbe7f..e85bf433760e 100644 --- a/test-data/unit/check-varargs.test +++ b/test-data/unit/check-varargs.test @@ -269,8 +269,8 @@ f(*(b, b, c)) # E: Argument 1 to "f" has incompatible type "*tuple[B, B, C]"; ex f(a, *(b, b)) # E: Argument 2 to "f" has incompatible type "*tuple[B, B]"; expected "C" f(b, *(b, c)) # E: Argument 1 to "f" has incompatible type "B"; expected "A" f(*(a, b)) # E: Missing positional arguments "b", "c" in call to "f" -f(*(a, b, c, c)) # E: Too many arguments for "f" -f(a, *(b, c, c)) # E: Too many arguments for "f" +f(*(a, b, c, c)) # E: Too many positional arguments for "f" +f(a, *(b, c, c)) # E: Too many positional arguments for "f" f(*(a, b, c)) f(a, *(b, c)) f(a, b, *(c,)) @@ -369,7 +369,7 @@ class A: pass d: Any a: A -f(a, a, *d) # E: Too many arguments for "f" +f(a, a, *d) # E: Too many positional arguments for "f" f(a, *d) # Ok f(*d) # Ok @@ -471,6 +471,606 @@ def foo() -> None: foo(*()) [builtins fixtures/tuple.pyi] +[case testVarArgsEquivalentTuples] +# https://github.com/python/mypy/issues/19692#issuecomment-3206968464 +from typing import Unpack + +def demo(x1: int, x2: str) -> None: ... + +def test( + x0: tuple[int, ...], + x1: tuple[str, ...], + y0: tuple[Unpack[tuple[int, ...]]], + y1: tuple[Unpack[tuple[str, ...]]], +) -> None: + # both should be either accepted or rejected + # TODO: maybe these should both be accepted? + # If we interpret `tuple[int, ...]` as `AnyOf[tuple[()], tuple[int], tuple[int, str], ...]` + # then there does exist an acceptable materialization + demo(*x0, *x1) # E: Argument 1 to "demo" has incompatible type "*tuple[int, ...]"; expected "str" + demo(*y0, *y1) # E: Argument 1 to "demo" has incompatible type "*tuple[Unpack[tuple[int, ...]]]"; expected "str" +[builtins fixtures/tuple.pyi] + + +[case testVarArgsVariableTuple] +from typing import Unpack +def takes_exactly_3(a1: str, a2: str, a3: str, /) -> None: ... +def takes_at_least3(a1: str, a2: str, a3: str, /, *args: str) -> None: ... + +def test_takes_at_least3( + x0: tuple[Unpack[tuple[str, ...]]], + x1: tuple[str, Unpack[tuple[str, ...]]], + x2: tuple[str, str, Unpack[tuple[str, ...]]], + x3: tuple[str, str, str, Unpack[tuple[str, ...]]], + x4: tuple[str, str, str, str, Unpack[tuple[str, ...]]], +) -> None: + takes_at_least3(*x0) + takes_at_least3(*x1) + takes_at_least3(*x2) + takes_at_least3(*x3) + takes_at_least3(*x4) + +def test_takes_exactly_3( + x0: tuple[Unpack[tuple[str, ...]]], + x1: tuple[str, Unpack[tuple[str, ...]]], + x2: tuple[str, str, Unpack[tuple[str, ...]]], + x3: tuple[str, str, str, Unpack[tuple[str, ...]]], + x4: tuple[str, str, str, str, Unpack[tuple[str, ...]]], +) -> None: + takes_exactly_3(*x0) + takes_exactly_3(*x1) + takes_exactly_3(*x2) + takes_exactly_3(*x3) + takes_exactly_3(*x4) # E: Too many positional arguments for "takes_exactly_3" +[builtins fixtures/tuple.pyi] + + +-- varargs + union arguments +-- ------------------------- + +[case testTupleConversionWithUnionStarArgs] +from typing import Union, List, Set, Tuple, Unpack, TypeVarTuple + +class A: pass +class B: pass +class C: pass +class D: pass + +Ts = TypeVarTuple('Ts') +def as_tuple(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]: + return (*args,) + +def test_union_same_size_tuple( + x1: Union[tuple[A, B], tuple[None, None]], + x2: Union[tuple[A, B], tuple[None, Unpack[tuple[None]]]], + x3: Union[tuple[A, B], tuple[None, None, Unpack[tuple[()]]]], + xx: tuple[Union[A, None], Union[B, None]], # reference type +) -> None: + reveal_type( as_tuple(*x1) ) # N: Revealed type is "tuple[__main__.A | None, __main__.B | None]" + reveal_type( as_tuple(*x2) ) # N: Revealed type is "tuple[__main__.A | None, __main__.B | None]" + reveal_type( as_tuple(*x3) ) # N: Revealed type is "tuple[__main__.A | None, __main__.B | None]" + reveal_type( as_tuple(*xx) ) # N: Revealed type is "tuple[__main__.A | None, __main__.B | None]" + +def test_union_different_size_tuple( + x1: Union[tuple[A, B], tuple[None]], + y1: tuple[Union[A, None], Unpack[tuple[B, ...]]], # reference type + x2: Union[tuple[A, B], tuple[None, None, None]], + y2: tuple[Union[A, None], Union[B, None], Unpack[tuple[None, ...]]], # reference type + x3: Union[tuple[A, B], tuple[None, None, None, Unpack[tuple[()]]]], + y3: tuple[Union[A, None], Union[B, None], Unpack[tuple[None, ...]]], # reference type +) -> None: + reveal_type( as_tuple(*x1) ) # N: Revealed type is "tuple[__main__.A | None, Unpack[builtins.tuple[__main__.B, ...]]]" + reveal_type( as_tuple(*y1) ) # N: Revealed type is "tuple[__main__.A | None, Unpack[builtins.tuple[__main__.B, ...]]]" + reveal_type( as_tuple(*x2) ) # N: Revealed type is "tuple[__main__.A | None, __main__.B | None, Unpack[builtins.tuple[None, ...]]]" + reveal_type( as_tuple(*y2) ) # N: Revealed type is "tuple[__main__.A | None, __main__.B | None, Unpack[builtins.tuple[None, ...]]]" + reveal_type( as_tuple(*x3) ) # N: Revealed type is "tuple[__main__.A | None, __main__.B | None, Unpack[builtins.tuple[None, ...]]]" + reveal_type( as_tuple(*y3) ) # N: Revealed type is "tuple[__main__.A | None, __main__.B | None, Unpack[builtins.tuple[None, ...]]]" + +def test_union_fixed_size_and_variadic_tuple( + x1: Union[tuple[A, B, C], tuple[None, ...]], + y1: tuple[Union[A, B, C, None], ...], # reference type + x2: Union[tuple[A, B, C], tuple[None, Unpack[tuple[None, ...]], None]], + y2: tuple[Union[A, None], Unpack[tuple[Union[B, None], ...]], Union[C, None]], # reference type +) -> None: + reveal_type( as_tuple(*x1) ) # N: Revealed type is "builtins.tuple[__main__.A | __main__.B | __main__.C | None, ...]" + reveal_type( as_tuple(*y1) ) # N: Revealed type is "builtins.tuple[__main__.A | __main__.B | __main__.C | None, ...]" + reveal_type( as_tuple(*x2) ) # N: Revealed type is "tuple[__main__.A | None, Unpack[builtins.tuple[__main__.B | None, ...]], __main__.C | None]" + reveal_type( as_tuple(*y2) ) # N: Revealed type is "tuple[__main__.A | None, Unpack[builtins.tuple[__main__.B | None, ...]], __main__.C | None]" + +def test_union_variable_size_tuples( + # same + subtype + tt1: Union[Tuple[Union[A, None], ...], Tuple[A, ...]], + ll1: Union[List[Union[A, None]], List[A]], + ss1: Union[Set[Union[A, None]], Set[A]], + # mixed + subtype + tl1: Union[Tuple[Union[A, None], ...], List[A]], + ts1: Union[Tuple[Union[A, None], ...], Set[A]], + ls1: Union[List[Union[A, None]], Set[A]], + # same + not subtype + tt2: Union[Tuple[A, ...], Tuple[B, ...]], + ll2: Union[List[A], List[B]], + ss2: Union[Set[A], Set[B]], + # mixed + not subtype + tl2: Union[Tuple[A, ...], List[B]], + ts2: Union[Tuple[A, ...], Set[B]], + ls2: Union[List[A], Set[B]], +) -> None: + reveal_type( as_tuple(*tt1) ) # N: Revealed type is "builtins.tuple[__main__.A | None, ...]" + reveal_type( as_tuple(*ll1) ) # N: Revealed type is "builtins.tuple[__main__.A | None, ...]" + reveal_type( as_tuple(*ss1) ) # N: Revealed type is "builtins.tuple[__main__.A | None, ...]" + reveal_type( as_tuple(*tl1) ) # N: Revealed type is "builtins.tuple[__main__.A | None, ...]" + reveal_type( as_tuple(*ts1) ) # N: Revealed type is "builtins.tuple[__main__.A | None, ...]" + reveal_type( as_tuple(*ls1) ) # N: Revealed type is "builtins.tuple[__main__.A | None, ...]" + reveal_type( as_tuple(*tt2) ) # N: Revealed type is "builtins.tuple[__main__.A | __main__.B, ...]" + reveal_type( as_tuple(*ll2) ) # N: Revealed type is "builtins.tuple[__main__.A | __main__.B, ...]" + reveal_type( as_tuple(*ss2) ) # N: Revealed type is "builtins.tuple[__main__.A | __main__.B, ...]" + reveal_type( as_tuple(*tl2) ) # N: Revealed type is "builtins.tuple[__main__.A | __main__.B, ...]" + reveal_type( as_tuple(*ts2) ) # N: Revealed type is "builtins.tuple[__main__.A | __main__.B, ...]" + reveal_type( as_tuple(*ls2) ) # N: Revealed type is "builtins.tuple[__main__.A | __main__.B, ...]" + +NESTED = Union[str, list[NESTED]] +def test_union_recursive(x: Union[list[Union[NESTED, None]], list[NESTED]]) -> None: + reveal_type( as_tuple(*x) ) # N: Revealed type is "builtins.tuple[builtins.str | builtins.list[builtins.str | builtins.list[...]] | None, ...]" +[builtins fixtures/primitives.pyi] + +[case testTupleExpressionWithUnionStarArgs] +from typing import Union, List, Set, Tuple, Unpack + +class A: pass +class B: pass +class C: pass +class D: pass + +def test_union_same_size_tuple( + x1: Union[tuple[A, B], tuple[None, None]], + x2: Union[tuple[A, B], tuple[None, Unpack[tuple[None]]]], + x3: Union[tuple[A, B], tuple[None, None, Unpack[tuple[()]]]], + y1: tuple[Union[A, None], Union[B, None]], # reference type +) -> None: + reveal_type( (*x1,) ) # N: Revealed type is "tuple[__main__.A | None, __main__.B | None]" + reveal_type( (*x2,) ) # N: Revealed type is "tuple[__main__.A | None, __main__.B | None]" + reveal_type( (*x3,) ) # N: Revealed type is "tuple[__main__.A | None, __main__.B | None]" + reveal_type( (*y1,) ) # N: Revealed type is "tuple[__main__.A | None, __main__.B | None]" + +def test_union_different_size_tuple( + x1: Union[tuple[A, B], tuple[None]], + y1: tuple[Union[A, None], Unpack[tuple[B, ...]]], # reference type + x2: Union[tuple[A, B], tuple[None, None, None]], + y2: tuple[Union[A, None], Union[B, None], Unpack[tuple[None, ...]]], # reference type + x3: Union[tuple[A, B], tuple[None, None, None, Unpack[tuple[()]]]], + y3: tuple[Union[A, None], Union[B, None], Unpack[tuple[None, ...]]], # reference type +) -> None: + reveal_type( (*x1,) ) # N: Revealed type is "tuple[__main__.A | None, Unpack[builtins.tuple[__main__.B, ...]]]" + reveal_type( (*y1,) ) # N: Revealed type is "tuple[__main__.A | None, Unpack[builtins.tuple[__main__.B, ...]]]" + reveal_type( (*x2,) ) # N: Revealed type is "tuple[__main__.A | None, __main__.B | None, Unpack[builtins.tuple[None, ...]]]" + reveal_type( (*y2,) ) # N: Revealed type is "tuple[__main__.A | None, __main__.B | None, Unpack[builtins.tuple[None, ...]]]" + reveal_type( (*x3,) ) # N: Revealed type is "tuple[__main__.A | None, __main__.B | None, Unpack[builtins.tuple[None, ...]]]" + reveal_type( (*y3,) ) # N: Revealed type is "tuple[__main__.A | None, __main__.B | None, Unpack[builtins.tuple[None, ...]]]" + +def test_union_fixed_size_and_variadic_tuple( + x1: Union[tuple[A, B, C], tuple[None, ...]], + y1: tuple[Union[A, B, C, None], ...], # reference type + x2: Union[tuple[A, B, C], tuple[None, Unpack[tuple[None, ...]], None]], + y2: tuple[Union[A, None], Unpack[tuple[Union[B, None], ...]], Union[C, None]], # reference type +) -> None: + reveal_type( (*x1,) ) # N: Revealed type is "builtins.tuple[__main__.A | __main__.B | __main__.C | None, ...]" + reveal_type( (*y1,) ) # N: Revealed type is "builtins.tuple[__main__.A | __main__.B | __main__.C | None, ...]" + reveal_type( (*x2,) ) # N: Revealed type is "tuple[__main__.A | None, Unpack[builtins.tuple[__main__.B | None, ...]], __main__.C | None]" + reveal_type( (*y2,) ) # N: Revealed type is "tuple[__main__.A | None, Unpack[builtins.tuple[__main__.B | None, ...]], __main__.C | None]" + +def test_union_variable_size_tuples( + # same + subtype + tt1: Union[Tuple[Union[A, None], ...], Tuple[A, ...]], + ll1: Union[List[Union[A, None]], List[A]], + ss1: Union[Set[Union[A, None]], Set[A]], + # mixed + subtype + tl1: Union[Tuple[Union[A, None], ...], List[A]], + ts1: Union[Tuple[Union[A, None], ...], Set[A]], + ls1: Union[List[Union[A, None]], Set[A]], + # same + not subtype + tt2: Union[Tuple[A, ...], Tuple[B, ...]], + ll2: Union[List[A], List[B]], + ss2: Union[Set[A], Set[B]], + # mixed + not subtype + tl2: Union[Tuple[A, ...], List[B]], + ts2: Union[Tuple[A, ...], Set[B]], + ls2: Union[List[A], Set[B]], +) -> None: + reveal_type( (*tt1,) ) # N: Revealed type is "builtins.tuple[__main__.A | None, ...]" + reveal_type( (*ll1,) ) # N: Revealed type is "builtins.tuple[__main__.A | None, ...]" + reveal_type( (*ss1,) ) # N: Revealed type is "builtins.tuple[__main__.A | None, ...]" + reveal_type( (*tl1,) ) # N: Revealed type is "builtins.tuple[__main__.A | None, ...]" + reveal_type( (*ts1,) ) # N: Revealed type is "builtins.tuple[__main__.A | None, ...]" + reveal_type( (*ls1,) ) # N: Revealed type is "builtins.tuple[__main__.A | None, ...]" + reveal_type( (*tt2,) ) # N: Revealed type is "builtins.tuple[__main__.A | __main__.B, ...]" + reveal_type( (*ll2,) ) # N: Revealed type is "builtins.tuple[__main__.A | __main__.B, ...]" + reveal_type( (*ss2,) ) # N: Revealed type is "builtins.tuple[__main__.A | __main__.B, ...]" + reveal_type( (*tl2,) ) # N: Revealed type is "builtins.tuple[__main__.A | __main__.B, ...]" + reveal_type( (*ts2,) ) # N: Revealed type is "builtins.tuple[__main__.A | __main__.B, ...]" + reveal_type( (*ls2,) ) # N: Revealed type is "builtins.tuple[__main__.A | __main__.B, ...]" + +NESTED = Union[str, list[NESTED]] +def test_union_recursive(x: Union[list[Union[NESTED, None]], list[NESTED]]) -> None: + reveal_type( (*x,) ) # N: Revealed type is "builtins.tuple[builtins.str | builtins.list[builtins.str | builtins.list[...]] | None, ...]" +[builtins fixtures/primitives.pyi] + + +[case testListExpressionWithUnionStarArgs] +from typing import Union, List, Set, Tuple, Unpack + +class A: pass +class B: pass +class C: pass +class D: pass + +def test_union_same_size_tuple( + x1: Union[tuple[A, B], tuple[None, None]], + x2: Union[tuple[A, B], tuple[None, Unpack[tuple[None]]]], + x3: Union[tuple[A, B], tuple[None, None, Unpack[tuple[()]]]], + xx: tuple[Union[A, None], Union[B, None]], # reference type +) -> None: + reveal_type( [*x1] ) # N: Revealed type is "builtins.list[__main__.A | None | __main__.B]" + reveal_type( [*x2] ) # N: Revealed type is "builtins.list[__main__.A | None | __main__.B]" + reveal_type( [*x3] ) # N: Revealed type is "builtins.list[__main__.A | None | __main__.B]" + reveal_type( [*xx] ) # N: Revealed type is "builtins.list[__main__.A | None | __main__.B]" + +def test_union_different_size_tuple( + y1: Union[tuple[A, B], tuple[None]], + y2: Union[tuple[A, B], tuple[None, None, None]], + y3: Union[tuple[A, B], tuple[None, None, None, Unpack[tuple[()]]]], + yy: tuple[Union[A, B, None], ...], # reference type +) -> None: + reveal_type( [*y1] ) # N: Revealed type is "builtins.list[__main__.B | __main__.A | None]" + reveal_type( [*y2] ) # N: Revealed type is "builtins.list[__main__.A | None | __main__.B]" + reveal_type( [*y3] ) # N: Revealed type is "builtins.list[__main__.A | None | __main__.B]" + reveal_type( [*yy] ) # N: Revealed type is "builtins.list[__main__.A | __main__.B | None]" + +def test_union_fixed_size_and_variadic_tuple( + z1: Union[tuple[A, B, C], tuple[None, ...]], + z2: Union[tuple[A, B, C], tuple[None, Unpack[tuple[None, ...]], None]], + zz: tuple[Union[A, B, C, None], ...], # reference type +) -> None: + reveal_type( [*z1] ) # N: Revealed type is "builtins.list[__main__.A | __main__.B | __main__.C | None]" + reveal_type( [*z2] ) # N: Revealed type is "builtins.list[__main__.A | None | __main__.B | __main__.C]" + reveal_type( [*zz] ) # N: Revealed type is "builtins.list[__main__.A | __main__.B | __main__.C | None]" + +def test_union_variable_size_tuples( + # same + subtype + tt1: Union[Tuple[Union[A, None], ...], Tuple[A, ...]], + ll1: Union[List[Union[A, None]], List[A]], + ss1: Union[Set[Union[A, None]], Set[A]], + # mixed + subtype + tl1: Union[Tuple[Union[A, None], ...], List[A]], + ts1: Union[Tuple[Union[A, None], ...], Set[A]], + ls1: Union[List[Union[A, None]], Set[A]], + # same + not subtype + tt2: Union[Tuple[A, ...], Tuple[B, ...]], + ll2: Union[List[A], List[B]], + ss2: Union[Set[A], Set[B]], + # mixed + not subtype + tl2: Union[Tuple[A, ...], List[B]], + ts2: Union[Tuple[A, ...], Set[B]], + ls2: Union[List[A], Set[B]], +) -> None: + reveal_type( [*tt1] ) # N: Revealed type is "builtins.list[__main__.A | None]" + reveal_type( [*ll1] ) # N: Revealed type is "builtins.list[__main__.A | None]" + reveal_type( [*ss1] ) # N: Revealed type is "builtins.list[__main__.A | None]" + reveal_type( [*tl1] ) # N: Revealed type is "builtins.list[__main__.A | None]" + reveal_type( [*ts1] ) # N: Revealed type is "builtins.list[__main__.A | None]" + reveal_type( [*ls1] ) # N: Revealed type is "builtins.list[__main__.A | None]" + reveal_type( [*tt2] ) # N: Revealed type is "builtins.list[__main__.A | __main__.B]" + reveal_type( [*ll2] ) # N: Revealed type is "builtins.list[__main__.A | __main__.B]" + reveal_type( [*ss2] ) # N: Revealed type is "builtins.list[__main__.A | __main__.B]" + reveal_type( [*tl2] ) # N: Revealed type is "builtins.list[__main__.A | __main__.B]" + reveal_type( [*ts2] ) # N: Revealed type is "builtins.list[__main__.A | __main__.B]" + reveal_type( [*ls2] ) # N: Revealed type is "builtins.list[__main__.A | __main__.B]" + +NESTED = Union[str, list[NESTED]] +def test_union_recursive(x: Union[list[Union[NESTED, None]], list[NESTED]]) -> None: + reveal_type( [*x] ) # N: Revealed type is "builtins.list[builtins.str | builtins.list[builtins.str | builtins.list[...]] | None]" +[builtins fixtures/primitives.pyi] + + +[case testSetExpressionWithUnionStarArgs] +from typing import Union, List, Set, Tuple, Unpack + +class A: pass +class B: pass +class C: pass +class D: pass + +def test_union_same_size_tuple( + x1: Union[tuple[A, B], tuple[None, None]], + x2: Union[tuple[A, B], tuple[None, Unpack[tuple[None]]]], + x3: Union[tuple[A, B], tuple[None, None, Unpack[tuple[()]]]], + xx: tuple[Union[A, None], Union[B, None]], # reference type +) -> None: + reveal_type( {*x1} ) # N: Revealed type is "builtins.set[__main__.A | None | __main__.B]" + reveal_type( {*x2} ) # N: Revealed type is "builtins.set[__main__.A | None | __main__.B]" + reveal_type( {*x3} ) # N: Revealed type is "builtins.set[__main__.A | None | __main__.B]" + reveal_type( {*xx} ) # N: Revealed type is "builtins.set[__main__.A | None | __main__.B]" + +def test_union_different_size_tuple( + y1: Union[tuple[A, B], tuple[None]], + y2: Union[tuple[A, B], tuple[None, None, None]], + y3: Union[tuple[A, B], tuple[None, None, None, Unpack[tuple[()]]]], + yy: tuple[Union[A, B, None], ...], # reference type +) -> None: + reveal_type( {*y1} ) # N: Revealed type is "builtins.set[__main__.B | __main__.A | None]" + reveal_type( {*y2} ) # N: Revealed type is "builtins.set[__main__.A | None | __main__.B]" + reveal_type( {*y3} ) # N: Revealed type is "builtins.set[__main__.A | None | __main__.B]" + reveal_type( {*yy} ) # N: Revealed type is "builtins.set[__main__.A | __main__.B | None]" + +def test_union_fixed_size_and_variadic_tuple( + z1: Union[tuple[A, B, C], tuple[None, ...]], + z2: Union[tuple[A, B, C], tuple[None, Unpack[tuple[None, ...]], None]], + zz: tuple[Union[A, B, C, None], ...], # reference type +) -> None: + reveal_type( {*z1} ) # N: Revealed type is "builtins.set[__main__.A | __main__.B | __main__.C | None]" + reveal_type( {*z2} ) # N: Revealed type is "builtins.set[__main__.A | None | __main__.B | __main__.C]" + reveal_type( {*zz} ) # N: Revealed type is "builtins.set[__main__.A | __main__.B | __main__.C | None]" + +def test_union_variable_size_tuples( + # same + subtype + tt1: Union[Tuple[Union[A, None], ...], Tuple[A, ...]], + ll1: Union[List[Union[A, None]], List[A]], + ss1: Union[Set[Union[A, None]], Set[A]], + # mixed + subtype + tl1: Union[Tuple[Union[A, None], ...], List[A]], + ts1: Union[Tuple[Union[A, None], ...], Set[A]], + ls1: Union[List[Union[A, None]], Set[A]], + # same + not subtype + tt2: Union[Tuple[A, ...], Tuple[B, ...]], + ll2: Union[List[A], List[B]], + ss2: Union[Set[A], Set[B]], + # mixed + not subtype + tl2: Union[Tuple[A, ...], List[B]], + ts2: Union[Tuple[A, ...], Set[B]], + ls2: Union[List[A], Set[B]], +) -> None: + reveal_type( {*tt1} ) # N: Revealed type is "builtins.set[__main__.A | None]" + reveal_type( {*ll1} ) # N: Revealed type is "builtins.set[__main__.A | None]" + reveal_type( {*ss1} ) # N: Revealed type is "builtins.set[__main__.A | None]" + reveal_type( {*tl1} ) # N: Revealed type is "builtins.set[__main__.A | None]" + reveal_type( {*ts1} ) # N: Revealed type is "builtins.set[__main__.A | None]" + reveal_type( {*ls1} ) # N: Revealed type is "builtins.set[__main__.A | None]" + reveal_type( {*tt2} ) # N: Revealed type is "builtins.set[__main__.A | __main__.B]" + reveal_type( {*ll2} ) # N: Revealed type is "builtins.set[__main__.A | __main__.B]" + reveal_type( {*ss2} ) # N: Revealed type is "builtins.set[__main__.A | __main__.B]" + reveal_type( {*tl2} ) # N: Revealed type is "builtins.set[__main__.A | __main__.B]" + reveal_type( {*ts2} ) # N: Revealed type is "builtins.set[__main__.A | __main__.B]" + reveal_type( {*ls2} ) # N: Revealed type is "builtins.set[__main__.A | __main__.B]" + +NESTED = Union[str, list[NESTED]] +def test_union_recursive(x: Union[list[Union[NESTED, None]], list[NESTED]]) -> None: + reveal_type( {*x} ) # N: Revealed type is "builtins.set[builtins.str | builtins.list[builtins.str | builtins.list[...]] | None]" +[builtins fixtures/primitives.pyi] + + +[case testStarArgsWithPaddedTupleArgument] +from typing import Union, TypeVarTuple, Unpack + +class A: pass +Ts = TypeVarTuple('Ts') + +def test_padded_tuple(padded_tuple: tuple[A, Unpack[Ts], A]) -> None: + # technically, this should be ``list[A | None | Union[*Ts]]`` + reveal_type( (*padded_tuple,) ) # N: Revealed type is "tuple[__main__.A, Unpack[Ts`-1], __main__.A]" + reveal_type( [*padded_tuple] ) # N: Revealed type is "builtins.list[Any]" + reveal_type( {*padded_tuple} ) # N: Revealed type is "builtins.set[Any]" + +def test_padded_union_typevartuple(padded_union: Union[tuple[A, Unpack[Ts], A], tuple[None, None, None]]) -> None: + # FIXME: https://github.com/python/mypy/issues/16720 + # We should have list[A | None | Union[*Ts]] here, but we have list[Any] + reveal_type( (*padded_union,) ) # N: Revealed type is "tuple[__main__.A | None, Unpack[builtins.tuple[Any | None, ...]], __main__.A | None]" + reveal_type( [*padded_union] ) # N: Revealed type is "builtins.list[__main__.A | None | Any]" + reveal_type( {*padded_union} ) # N: Revealed type is "builtins.set[__main__.A | None | Any]" + +def test_padded_union_head( + x: Union[tuple[None, A, A, Unpack[tuple[None, ...]]], tuple[None, A, Unpack[tuple[None, ...]]]], + y: tuple[None, A, Unpack[tuple[Union[A, None], ...]]], # reference type +) -> None: + reveal_type( (*x,) ) # N: Revealed type is "tuple[None, __main__.A, Unpack[builtins.tuple[__main__.A | None, ...]]]" + reveal_type( (*y,) ) # N: Revealed type is "tuple[None, __main__.A, Unpack[builtins.tuple[__main__.A | None, ...]]]" + +def test_padded_union_tail( + x: Union[tuple[Unpack[tuple[None, ...]], A, A, None], tuple[Unpack[tuple[None, ...]], A, None]], + y: tuple[Unpack[tuple[Union[A, None], ...]], A, None], # reference type +) -> None: + reveal_type( (*x,) ) # N: Revealed type is "tuple[Unpack[builtins.tuple[None | __main__.A, ...]], __main__.A, None]" + reveal_type( (*y,) ) # N: Revealed type is "tuple[Unpack[builtins.tuple[__main__.A | None, ...]], __main__.A, None]" +[builtins fixtures/primitives.pyi] + + +[case testStarArgsWithPaddedTupleArgumentUpcast] +# https://github.com/python/mypy/issues/19659 +from typing import Iterable, TypeVarTuple, Unpack, Union, TypeVar + +class A: pass +Ts = TypeVarTuple('Ts') +T = TypeVar('T') + +def upcast(arg: Iterable[T]) -> Iterable[T]: return arg + +def test_padded_tuple(padded_tuple: tuple[A, Unpack[Ts], A]) -> None: + as_iterable = upcast(padded_tuple) + reveal_type(as_iterable) # N: Revealed type is "typing.Iterable[Any]" + +def test_padded_union(padded_union: Union[tuple[A, Unpack[Ts], A], tuple[None, None, None]]) -> None: + as_iterable = upcast(padded_union) + reveal_type(as_iterable) # N: Revealed type is "typing.Iterable[Any]" +[builtins fixtures/primitives.pyi] + + +[case testStarArgsVariableTupleAssignable] +# https://github.com/python/mypy/issues/19692 +from typing import Unpack + +def takes_at_least3(a1: str, a2: str, a3: str, *args: str) -> None: ... + +def test( + x0: tuple[str, ...], + x1: tuple[str, Unpack[tuple[str, ...]]], + x2: tuple[str, str, Unpack[tuple[str, ...]]], + x3: tuple[str, str, str, Unpack[tuple[str, ...]]], +) -> None: + takes_at_least3(*x0) + takes_at_least3(*x1) + takes_at_least3(*x2) + takes_at_least3(*x3) +[builtins fixtures/primitives.pyi] + + +[case testStarArgsTupleExpressionVersusTypeVarTuple] +# flags: --enable-incomplete-feature=PreciseTupleTypes +# https://github.com/python/mypy/issues/19665 +from typing import Unpack, TypeVarTuple + +Ts = TypeVarTuple('Ts') + +def as_tuple(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]: return args + +def test(one: int, many: tuple[int, ...]) -> None: + reveal_type( (one, *many, one) ) # N: Revealed type is "tuple[builtins.int, Unpack[builtins.tuple[builtins.int, ...]], builtins.int]" + reveal_type( as_tuple(one, *many, one) ) # N: Revealed type is "tuple[builtins.int, Unpack[builtins.tuple[builtins.int, ...]], builtins.int]" +[builtins fixtures/tuple.pyi] + + +[case testStarArgsWithUnion] +from typing import Union + +class A: pass +class B: pass +class C: pass +class D: pass + +def f(a: Union[A, None], b: Union[B, None], c: Union[C, None]) -> None: pass +def g(a: Union[A, None], b: Union[B, None], c: Union[C, None], d: Union[D, None] = None) -> None: pass + +def test( + x: Union[tuple[A, B, C], tuple[None, None, None]], +) -> None: + f(*x) + g(*x) +[builtins fixtures/tuple.pyi] + + +[case testStarArgsWithUnionSameTupleLengths] +from typing import Union + +class A: pass +class B: pass +class C: pass +class D: pass + +def test_good_case( + x: Union[tuple[A, B, C], tuple[None, None, None]], + y: tuple[Union[A, None], Union[B, None], Union[C, None]], +) -> None: + def f(a: Union[A, None], b: Union[B, None], c: Union[C, None]) -> None: pass + def g(a: Union[A, None], b: Union[B, None], c: Union[C, None], d: Union[D, None] = None) -> None: pass + f(*x) + f(*y) + g(*x) + g(*y) + +def test_bad_case( + x: Union[tuple[A, A, A], tuple[None, None, None]], + y: tuple[Union[A, None], Union[A, None], Union[A, None]], +) -> None: + def f(a: Union[A, None], b: Union[B, None], c: Union[C, None]) -> None: pass + def g(a: Union[A, None], b: Union[B, None], c: Union[C, None], d: Union[D, None] = None) -> None: pass + f(*x) # E: Argument 1 to "f" has incompatible type "*tuple[A, A, A] | tuple[None, None, None]"; expected "B | None" \ + # E: Argument 1 to "f" has incompatible type "*tuple[A, A, A] | tuple[None, None, None]"; expected "C | None" + f(*y) # E: Argument 1 to "f" has incompatible type "*tuple[A | None, A | None, A | None]"; expected "B | None" \ + # E: Argument 1 to "f" has incompatible type "*tuple[A | None, A | None, A | None]"; expected "C | None" + g(*x) # E: Argument 1 to "g" has incompatible type "*tuple[A, A, A] | tuple[None, None, None]"; expected "B | None" \ + # E: Argument 1 to "g" has incompatible type "*tuple[A, A, A] | tuple[None, None, None]"; expected "C | None" + g(*y) # E: Argument 1 to "g" has incompatible type "*tuple[A | None, A | None, A | None]"; expected "B | None" \ + # E: Argument 1 to "g" has incompatible type "*tuple[A | None, A | None, A | None]"; expected "C | None" + + + + + +[builtins fixtures/tuple.pyi] + + +[case testStarArgsWithUnionSameTupleLengthsOverload] +from typing import Union, overload + +class A: pass +class B: pass +class C: pass +class D: pass + +@overload +def f(a: A, b: B, c: C) -> None: ... +@overload +def f(a: None, b: None, c: None) -> None: ... +def f(a: Union[A, None], b: Union[B, None], c: Union[C, None]) -> None: pass + +def test_case( + good: Union[tuple[A, B, C], tuple[None, None, None]], + bad1: Union[tuple[None, B, C], tuple[A, None, None]], + bad2: tuple[Union[A, None], Union[B, None], Union[C, None]], +) -> None: + f(*good) + f(*bad1) # E: Argument 1 to "f" has incompatible type "*tuple[None, B, C] | tuple[A, None, None]"; expected "A" \ + # E: Argument 1 to "f" has incompatible type "*tuple[None, B, C] | tuple[A, None, None]"; expected "B" \ + # E: Argument 1 to "f" has incompatible type "*tuple[None, B, C] | tuple[A, None, None]"; expected "C" + f(*bad2) # E: Argument 1 to "f" has incompatible type "*tuple[A | None, B | None, C | None]"; expected "A" \ + # E: Argument 1 to "f" has incompatible type "*tuple[A | None, B | None, C | None]"; expected "B" \ + # E: Argument 1 to "f" has incompatible type "*tuple[A | None, B | None, C | None]"; expected "C" + + + + + +[builtins fixtures/tuple.pyi] + + +[case testStarArgsWithUnionDifferentTupleLengths] +from typing import Union, Unpack + +class A: pass +class B: pass +class C: pass +class D: pass + +def test_good_case( + x: Union[tuple[A, A, A], tuple[None, None]], + y: tuple[Union[A, None], ...], # reference type +) -> None: + def f(a: Union[A, None], b: Union[A, None], c: Union[A, None] = None) -> None: pass + def g(a: Union[A, None], b: Union[A, None], c: Union[A, None] = None, d: Union[A, None] = None) -> None: pass + f(*x) + f(*y) + g(*x) + g(*y) + +def test_bad_case( + x: Union[tuple[A, B, C], tuple[None, None]], + y: tuple[Union[A, None], Union[B, None], Unpack[tuple[C, ...]]], # reference type +) -> None: + def f(a: Union[A, None], b: Union[A, None], c: Union[A, None]) -> None: pass + def g(a: Union[A, None], b: Union[A, None], c: Union[A, None] = None, d: Union[A, None] = None) -> None: pass + # TODO: Show the coerced join type. + f(*x) # E: Argument 1 to "f" has incompatible type "*tuple[A, B, C] | tuple[None, None]"; expected "A | None" + f(*y) # E: Argument 1 to "f" has incompatible type "*tuple[A | None, B | None, Unpack[tuple[C, ...]]]"; expected "A | None" + g(*x) # E: Argument 1 to "g" has incompatible type "*tuple[A, B, C] | tuple[None, None]"; expected "A | None" + g(*y) # E: Argument 1 to "g" has incompatible type "*tuple[A | None, B | None, Unpack[tuple[C, ...]]]"; expected "A | None" +[builtins fixtures/tuple.pyi] + +[case testListExpressionWithListSubtypeStarArgs] +# https://github.com/python/mypy/issues/19662 +class MyList(list[int]): ... + +def test(x: MyList, y: list[int]) -> None: + reveal_type( [*x] ) # N: Revealed type is "builtins.list[builtins.int]" + reveal_type( [*y] ) # N: Revealed type is "builtins.list[builtins.int]" +[builtins fixtures/list.pyi] + -- Overloads + varargs -- ------------------- @@ -595,7 +1195,7 @@ if int(): if int(): b, b = f(a, *(b,)) # E: Argument 1 to "f" has incompatible type "A"; expected "B" if int(): - a, b = f(*(a, b, b)) # E: Too many arguments for "f" + a, b = f(*(a, b, b)) # E: Too many positional arguments for "f" if int(): a, b = f(*(a, b)) if int(): @@ -1130,3 +1730,131 @@ foo(key="yes", value="ok") bad: Callable[[*TD], None] # E: "TD" cannot be unpacked (must be tuple or TypeVarTuple) [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] + +[case testStarArgsWithConcatenateParamSpec] +from typing import Callable +from typing_extensions import Concatenate, ParamSpec + +P = ParamSpec('P') + +def test( + fn: Callable[Concatenate[int, P], int], + *args: P.args, + **kwargs: P.kwargs, +) -> None: + fn(*(1, *args), **kwargs) +[builtins fixtures/tuple.pyi] + + +[case testStarArgsWithSuffix] +from typing import Unpack + +def f(*args: Unpack[tuple[Unpack[tuple[str, ...]], int, int, int]]) -> None: pass + +def test(x: tuple[str, ...], y: tuple[int, int, int], z: int) -> None: + f(*x, z, z, z) + f(*(*x, z, z, z)) + f(*x, *y) + f(*(*x, *y)) +[builtins fixtures/tuple.pyi] + + +[case testStarArgsWithPrefixAndSuffix] +from typing import Unpack + +class P: ... +class V: ... +class S: ... + +def f( + a1: P, a2: P, /, # prefix + *args: Unpack[tuple[Unpack[tuple[V, ...]], S, S]], # unbounded + suffix +) -> None: pass + +def test_good_cases1( + p: P, + v: tuple[V, ...], + s: S, +) -> None: + # check all possible bracketing combinations + # there are 45 possible ways to parenthesize the arguments + # (little Schroeder number, see https://oeis.org/A001003) + f(p, p, *v, s, s) + f(p, p, *v, *(s, s)) + f(p, p, *(*v, s), s) + f(p, p, *(*v, s, s)) + f(p, p, *(*v, *(s, s))) + f(p, p, *(*(*v, s), s)) + f(p, *(p, *v), s, s) + f(p, *(p, *v), *(s, s)) + f(p, *(p, *v, s), s) + f(p, *(p, *(*v, s)), s) + f(p, *(*(p, *v), s), s) + f(p, *(p, *v, s, s)) + f(p, *(p, *v, *(s, s))) + f(p, *(p, *(*v, s), s)) + f(p, *(p, *(*v, s, s))) + f(p, *(p, *(*v, *(s, s)))) + f(p, *(p, *(*(*v, s), s))) + f(p, *(*(p, *v), s, s)) + f(p, *(*(p, *v), *(s, s))) + f(p, *(*(p, *v, s), s)) + f(p, *(*(p, *(*v, s)), s)) + f(p, *(*(*(p, *v), s), s)) + f(*(p, p), *v, s, s) + f(*(p, p), *v, *(s, s)) + f(*(p, p), *(*v, s), s) + f(*(p, p), *(*v, s, s)) + f(*(p, p), *(*v, *(s, s))) + f(*(p, p), *(*(*v, s), s)) + f(*(p, p, *v), s, s) + f(*(p, *(p, *v)), s, s) + f(*(*(p, p), *v), s, s) + f(*(p, p, *v), *(s, s)) + f(*(p, *(p, *v)), *(s, s)) + f(*(*(p, p), *v), *(s, s)) + f(*(p, p, *v, s), s) + f(*(p, p, *(*v, s)), s) + f(*(p, *(p, *v), s), s) + f(*(p, *(p, *v, s)), s) + f(*(p, *(p, *(*v, s))), s) + f(*(p, *(*(p, *v), s)), s) + f(*(*(p, p), *v, s), s) + f(*(*(p, p), *(*v, s)), s) + f(*(*(p, p, *v), s), s) + f(*(*(p, *(p, *v)), s), s) + f(*(*(*(p, p), *v), s), s) +[builtins fixtures/tuple.pyi] + + +[case testVarArgsNumber] +from typing import Unpack +def takes_exactly_3(a1: str, a2: str, a3: str, /) -> None: ... +def takes_at_least3(a1: str, a2: str, a3: str, /, *args: str) -> None: ... + +def test_takes_at_least3( + x0: tuple[Unpack[tuple[str, ...]]], + x1: tuple[str, Unpack[tuple[str, ...]]], + x2: tuple[str, str, Unpack[tuple[str, ...]]], + x3: tuple[str, str, str, Unpack[tuple[str, ...]]], + x4: tuple[str, str, str, str, Unpack[tuple[str, ...]]], +) -> None: + takes_at_least3(*x0) + takes_at_least3(*x1) + takes_at_least3(*x2) + takes_at_least3(*x3) + takes_at_least3(*x4) + +def test_takes_exactly_3( + x0: tuple[Unpack[tuple[str, ...]]], + x1: tuple[str, Unpack[tuple[str, ...]]], + x2: tuple[str, str, Unpack[tuple[str, ...]]], + x3: tuple[str, str, str, Unpack[tuple[str, ...]]], + x4: tuple[str, str, str, str, Unpack[tuple[str, ...]]], +) -> None: + takes_exactly_3(*x0) + takes_exactly_3(*x1) + takes_exactly_3(*x2) + takes_exactly_3(*x3) + takes_exactly_3(*x4) # E: Too many positional arguments for "takes_exactly_3" +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/fine-grained-blockers.test b/test-data/unit/fine-grained-blockers.test index 0b2d9d2fcb5f..be73c623bbfd 100644 --- a/test-data/unit/fine-grained-blockers.test +++ b/test-data/unit/fine-grained-blockers.test @@ -98,13 +98,13 @@ def f() -> None [file a.py.3] def f() -> None: pass [out] -main:3: error: Too many arguments for "f" -main:5: error: Too many arguments for "f" +main:3: error: Too many positional arguments for "f" +main:5: error: Too many positional arguments for "f" == a.py:1: error: Expected ':' == -main:3: error: Too many arguments for "f" -main:5: error: Too many arguments for "f" +main:3: error: Too many positional arguments for "f" +main:5: error: Too many positional arguments for "f" [case testUpdateClassReferenceAcrossBlockingError] import a @@ -144,14 +144,14 @@ main:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missin == a.py:1: error: Invalid syntax == -main:2: error: Too many arguments for "f" +main:2: error: Too many positional arguments for "f" [out version==3.10.0] main:1: error: Cannot find implementation or library stub for module named "a" main:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports == a.py:1: error: Invalid syntax. Perhaps you forgot a comma? == -main:2: error: Too many arguments for "f" +main:2: error: Too many positional arguments for "f" [case testModifyTwoFilesOneWithBlockingError1] import a @@ -276,8 +276,8 @@ a.py:1: error: Invalid syntax == a.py:1: error: Invalid syntax == -a.py:3: error: Too many arguments for "g" -b.py:3: error: Too many arguments for "f" +a.py:3: error: Too many positional arguments for "g" +b.py:3: error: Too many positional arguments for "f" [case testDeleteFileWithBlockingError-only_when_nocache] -- Different cache/no-cache tests because: diff --git a/test-data/unit/fine-grained-follow-imports.test b/test-data/unit/fine-grained-follow-imports.test index d716a57123dc..d8e323d7a818 100644 --- a/test-data/unit/fine-grained-follow-imports.test +++ b/test-data/unit/fine-grained-follow-imports.test @@ -539,7 +539,7 @@ main.py:2: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#mis main.py:3: error: Missing positional argument "x" in call to "f" == main.py:3: error: Missing positional argument "x" in call to "f" -main.py:4: error: Too many arguments for "f" +main.py:4: error: Too many positional arguments for "f" [case testFollowImportsNormalPackageInitFile4-only_when_cache] # flags: --follow-imports=normal @@ -717,7 +717,7 @@ def f() -> None: pass [out] == -main.py:2: error: Too many arguments for "f" +main.py:2: error: Too many positional arguments for "f" [case testFollowImportsNormalMultipleImportedModulesSpecialCase] # flags: --follow-imports=normal diff --git a/test-data/unit/fine-grained-modules.test b/test-data/unit/fine-grained-modules.test index 3ee07a03792f..327ad95a37c3 100644 --- a/test-data/unit/fine-grained-modules.test +++ b/test-data/unit/fine-grained-modules.test @@ -21,7 +21,7 @@ a.f(1) [out] == == -b.py:2: error: Too many arguments for "f" +b.py:2: error: Too many positional arguments for "f" [case testAddFileWithErrors] import b @@ -39,7 +39,7 @@ def f(x: int) -> None: pass a.py:2: error: Incompatible return value type (got "int", expected "str") == a.py:2: error: Incompatible return value type (got "int", expected "str") -b.py:2: error: Too many arguments for "f" +b.py:2: error: Too many positional arguments for "f" == [case testAddFileFixesError] @@ -74,7 +74,7 @@ b.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missin b.py:1: error: Cannot find implementation or library stub for module named "a" b.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports == -b.py:2: error: Too many arguments for "f" +b.py:2: error: Too many positional arguments for "f" [case testAddFileFixesAndGeneratesError2] import b @@ -94,7 +94,7 @@ b.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missin b.py:1: error: Cannot find implementation or library stub for module named "a" b.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports == -b.py:2: error: Too many arguments for "f" +b.py:2: error: Too many positional arguments for "f" [case testAddFileGeneratesError1] # flags: --ignore-missing-imports @@ -106,7 +106,7 @@ f(1) def f() -> None: pass [out] == -a.py:2: error: Too many arguments for "f" +a.py:2: error: Too many positional arguments for "f" [case testAddFilePreservesError1] import b @@ -571,7 +571,7 @@ def g() -> None: pass [out] == main:3: error: Missing positional argument "x" in call to "f" -main:4: error: Too many arguments for "g" +main:4: error: Too many positional arguments for "g" [case testModifyTwoFilesErrorsInBoth] import a @@ -594,7 +594,7 @@ a.f() [out] == b.py:3: error: Missing positional argument "x" in call to "f" -a.py:3: error: Too many arguments for "g" +a.py:3: error: Too many positional arguments for "g" [case testModifyTwoFilesFixErrorsInBoth] import a @@ -616,7 +616,7 @@ def g(x: int) -> None: pass a.f() [out] b.py:3: error: Missing positional argument "x" in call to "f" -a.py:3: error: Too many arguments for "g" +a.py:3: error: Too many positional arguments for "g" == [case testAddTwoFilesNoError] @@ -660,8 +660,8 @@ a.py:1: error: Cannot find implementation or library stub for module named "b" a.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports a.py:2: error: Cannot find implementation or library stub for module named "c" == -c.py:3: error: Too many arguments for "f" -b.py:3: error: Too many arguments for "g" +c.py:3: error: Too many positional arguments for "f" +b.py:3: error: Too many positional arguments for "g" [case testAddTwoFilesErrorsElsewhere] import a @@ -677,8 +677,8 @@ main:1: error: Cannot find implementation or library stub for module named "a" main:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports main:2: error: Cannot find implementation or library stub for module named "b" == -main:3: error: Too many arguments for "f" -main:4: error: Too many arguments for "g" +main:3: error: Too many positional arguments for "f" +main:4: error: Too many positional arguments for "g" [case testDeleteTwoFilesErrorsElsewhere] import a @@ -731,8 +731,8 @@ a.f(1) [delete a.py.2] [delete b.py.2] [out] -b.py:3: error: Too many arguments for "f" -a.py:3: error: Too many arguments for "g" +b.py:3: error: Too many positional arguments for "f" +a.py:3: error: Too many positional arguments for "g" == main:1: error: Cannot find implementation or library stub for module named "a" main:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports @@ -784,7 +784,7 @@ c.f(1) [out] == == -a.py:2: error: Too many arguments for "f" +a.py:2: error: Too many positional arguments for "f" [case testDeleteFileWSuperClass] # flags: --ignore-missing-imports @@ -825,7 +825,7 @@ def g() -> None: pass [delete m/x.py.2] [builtins fixtures/module.pyi] [out] -a.py:2: error: Too many arguments for "g" +a.py:2: error: Too many positional arguments for "g" == a.py:1: error: Cannot find implementation or library stub for module named "m.x" a.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports @@ -1247,7 +1247,7 @@ def foo(x: int) -> None: pass [out] == -a.py:2: error: Too many arguments for "foo" +a.py:2: error: Too many positional arguments for "foo" == [case testAddModuleAfterCache2-only_when_cache] @@ -1272,7 +1272,7 @@ def foo(x: int) -> None: pass [out] == -a.py:2: error: Too many arguments for "foo" +a.py:2: error: Too many positional arguments for "foo" == [case testAddModuleAfterCache3-only_when_cache] @@ -1305,7 +1305,7 @@ def foo(x: int) -> None: pass [out] == -a.py:2: error: Too many arguments for "foo" +a.py:2: error: Too many positional arguments for "foo" == @@ -1325,7 +1325,7 @@ a.foo(10) def foo(x: int) -> None: pass [out] == -b.py:2: error: Too many arguments for "foo" +b.py:2: error: Too many positional arguments for "foo" == [case testAddModuleAfterCache5-only_when_cache] @@ -1352,7 +1352,7 @@ def foo(x: int) -> None: pass [out] == -b.py:2: error: Too many arguments for "foo" +b.py:2: error: Too many positional arguments for "foo" == [case testAddModuleAfterCache6-only_when_cache] @@ -1380,7 +1380,7 @@ def foo(x: int) -> None: pass [out] == -a.py:2: error: Too many arguments for "foo" +a.py:2: error: Too many positional arguments for "foo" == [case testRenameAndDeleteModuleAfterCache-only_when_cache] diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 070d780e93f6..efcea8e295e5 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -257,7 +257,7 @@ class A: def f(self, a: 'A') -> None: pass [out] == -main:3: error: Too many arguments for "f" of "A" +main:3: error: Too many positional arguments for "f" of "A" == [case testFixTypeError2] @@ -1385,7 +1385,7 @@ class A: class A: pass [out] == -main:4: error: Too many arguments for "A" +main:4: error: Too many positional arguments for "A" [case testBaseClassConstructorChanged] import m @@ -1845,14 +1845,14 @@ def g() -> None: 3: , , , a 4: a.g [out] -a.py:11: error: Too many arguments for "h" +a.py:11: error: Too many positional arguments for "h" == a.py:10: error: Cannot find implementation or library stub for module named "b" a.py:10: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports == -a.py:11: error: Too many arguments for "h" +a.py:11: error: Too many positional arguments for "h" == -a.py:11: error: Too many arguments for "h" +a.py:11: error: Too many positional arguments for "h" [case testDecoratorSpecialCase2] import a @@ -6214,7 +6214,7 @@ class C: [out] == == -a.py:4: error: Too many arguments for "C" +a.py:4: error: Too many positional arguments for "C" -- Protocol tests @@ -10518,7 +10518,7 @@ m.py:8: error: Argument 2 has incompatible type "int"; expected "str" == m.py:4: note: Revealed type is "functools.partial[builtins.int]" m.py:8: error: Argument 1 to "foo" has incompatible type "int"; expected "str" -m.py:9: error: Too many arguments for "foo" +m.py:9: error: Too many positional arguments for "foo" m.py:9: error: Argument 1 to "foo" has incompatible type "int"; expected "str" m.py:9: error: Argument 2 to "foo" has incompatible type "str"; expected "int" m.py:10: error: Unexpected keyword argument "a" for "foo" diff --git a/test-data/unit/fixtures/__init_subclass__.pyi b/test-data/unit/fixtures/__init_subclass__.pyi index b4618c28249e..8cd56393de0a 100644 --- a/test-data/unit/fixtures/__init_subclass__.pyi +++ b/test-data/unit/fixtures/__init_subclass__.pyi @@ -1,7 +1,5 @@ # builtins stub with object.__init_subclass__ -from typing import Mapping, Iterable # needed for ArgumentInferContext - class object: def __init_subclass__(cls) -> None: pass @@ -12,3 +10,10 @@ class bool: pass class str: pass class function: pass class dict: pass + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/__new__.pyi b/test-data/unit/fixtures/__new__.pyi index 57d3624ce92c..154f4cfc6c8b 100644 --- a/test-data/unit/fixtures/__new__.pyi +++ b/test-data/unit/fixtures/__new__.pyi @@ -18,3 +18,10 @@ class bool: pass class str: pass class function: pass class dict: pass + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/alias.pyi b/test-data/unit/fixtures/alias.pyi index 2ec7703f00c4..7c977e723080 100644 --- a/test-data/unit/fixtures/alias.pyi +++ b/test-data/unit/fixtures/alias.pyi @@ -1,7 +1,5 @@ # Builtins test fixture with a type alias 'bytes' -from typing import Mapping, Iterable # needed for `ArgumentInferContext` - class object: def __init__(self) -> None: pass class type: @@ -14,3 +12,10 @@ class function: pass bytes = str class dict: pass + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/any.pyi b/test-data/unit/fixtures/any.pyi index b1f8d83bf524..8a9ca005d6b7 100644 --- a/test-data/unit/fixtures/any.pyi +++ b/test-data/unit/fixtures/any.pyi @@ -8,3 +8,10 @@ class str: pass def any(i: Iterable[T]) -> bool: pass class dict: pass + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/async_await.pyi b/test-data/unit/fixtures/async_await.pyi index 96ade881111b..077d3fd2c19e 100644 --- a/test-data/unit/fixtures/async_await.pyi +++ b/test-data/unit/fixtures/async_await.pyi @@ -17,10 +17,16 @@ class str: pass class bool(int): pass class dict(typing.Generic[T, U]): pass class set(typing.Generic[T]): pass -class tuple(typing.Generic[T]): pass class BaseException: pass class StopIteration(BaseException): pass class StopAsyncIteration(BaseException): pass def iter(obj: typing.Any) -> typing.Any: pass def next(obj: typing.Any) -> typing.Any: pass class ellipsis: ... + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/bool.pyi b/test-data/unit/fixtures/bool.pyi index bc58a22b952b..bea789503bd1 100644 --- a/test-data/unit/fixtures/bool.pyi +++ b/test-data/unit/fixtures/bool.pyi @@ -8,7 +8,6 @@ class object: def __ne__(self, other: object) -> bool: pass class type: pass -class tuple(Generic[T]): pass class function: pass class int: pass class bool(int): pass @@ -18,3 +17,10 @@ class ellipsis: pass class list(Generic[T]): pass class property: pass class dict: pass + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/callable.pyi b/test-data/unit/fixtures/callable.pyi index 44abf0691ceb..8257fd0ba7f8 100644 --- a/test-data/unit/fixtures/callable.pyi +++ b/test-data/unit/fixtures/callable.pyi @@ -8,8 +8,6 @@ class object: class type: def __init__(self, x) -> None: pass -class tuple(Generic[T]): pass - class classmethod: pass class staticmethod: pass class function: pass @@ -29,3 +27,10 @@ class str: class ellipsis: pass class list: ... class dict: pass + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/classmethod.pyi b/test-data/unit/fixtures/classmethod.pyi index 97e018b1dc1c..a2b09e657587 100644 --- a/test-data/unit/fixtures/classmethod.pyi +++ b/test-data/unit/fixtures/classmethod.pyi @@ -25,7 +25,12 @@ class bytes: pass class bool: pass class ellipsis: pass -class tuple(typing.Generic[_T]): pass - class list: pass class dict: pass + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/complex.pyi b/test-data/unit/fixtures/complex.pyi index 880ec3dd4d9d..bdd5fc52bd1f 100644 --- a/test-data/unit/fixtures/complex.pyi +++ b/test-data/unit/fixtures/complex.pyi @@ -11,3 +11,10 @@ class float: pass class complex: pass class str: pass class dict: pass + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/complex_tuple.pyi b/test-data/unit/fixtures/complex_tuple.pyi index 81f1d33d1207..82c3a6c22378 100644 --- a/test-data/unit/fixtures/complex_tuple.pyi +++ b/test-data/unit/fixtures/complex_tuple.pyi @@ -1,11 +1,9 @@ -from typing import Generic, TypeVar +from typing import TypeVar _T = TypeVar('_T') class object: def __init__(self): pass -class tuple(Generic[_T]): pass - class type: pass class function: pass class int: pass @@ -14,3 +12,10 @@ class complex: pass class str: pass class ellipsis: pass class dict: pass + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/dataclasses.pyi b/test-data/unit/fixtures/dataclasses.pyi index 29f87ae97e62..9903868b3fbf 100644 --- a/test-data/unit/fixtures/dataclasses.pyi +++ b/test-data/unit/fixtures/dataclasses.pyi @@ -18,7 +18,6 @@ class object: class type: pass class ellipsis: pass -class tuple(Generic[_T]): pass class int: pass class float: pass class bytes: pass @@ -54,3 +53,10 @@ class function: pass class classmethod: pass class staticmethod: pass property = object() + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/dict-full.pyi b/test-data/unit/fixtures/dict-full.pyi index f20369ce9332..45dd6862ca87 100644 --- a/test-data/unit/fixtures/dict-full.pyi +++ b/test-data/unit/fixtures/dict-full.pyi @@ -69,7 +69,6 @@ class list(Sequence[T]): # needed by some test cases def __contains__(self, item: object) -> bool: pass def append(self, item: T) -> None: pass -class tuple(Generic[T]): pass class function: pass class float: pass class complex: pass @@ -81,3 +80,10 @@ def isinstance(x: object, t: Union[type, Tuple[type, ...]]) -> bool: pass class BaseException: pass def iter(__iterable: Iterable[T]) -> Iterator[T]: pass + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/dict.pyi b/test-data/unit/fixtures/dict.pyi index ed2287511161..aed9b1251326 100644 --- a/test-data/unit/fixtures/dict.pyi +++ b/test-data/unit/fixtures/dict.pyi @@ -52,7 +52,6 @@ class list(Sequence[T]): # needed by some test cases def __contains__(self, item: object) -> bool: pass def append(self, item: T) -> None: pass -class tuple(Generic[T]): pass class function: pass class float: pass class complex: pass @@ -62,3 +61,10 @@ class BaseException: pass def isinstance(x: object, t: Union[type, Tuple[type, ...]]) -> bool: pass def iter(__iterable: Iterable[T]) -> Iterator[T]: pass + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/divmod.pyi b/test-data/unit/fixtures/divmod.pyi index 4d81d8fb47a2..2a8dfe428409 100644 --- a/test-data/unit/fixtures/divmod.pyi +++ b/test-data/unit/fixtures/divmod.pyi @@ -11,7 +11,6 @@ class float(SupportsInt): def __rdivmod__(self, other: float) -> Tuple[float, float]: pass -class tuple: pass class function: pass class str: pass class type: pass @@ -21,3 +20,10 @@ _N = TypeVar('_N', int, float) def divmod(_x: _N, _y: _N) -> Tuple[_N, _N]: ... class dict: pass + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/enum.pyi b/test-data/unit/fixtures/enum.pyi index 22e7193da041..e72904c78346 100644 --- a/test-data/unit/fixtures/enum.pyi +++ b/test-data/unit/fixtures/enum.pyi @@ -1,5 +1,5 @@ # Minimal set of builtins required to work with Enums -from typing import TypeVar, Generic, Iterator, Sequence, overload, Iterable +from typing import TypeVar, Iterator, Sequence, overload, Iterable T = TypeVar('T') @@ -7,8 +7,10 @@ class object: def __init__(self): pass class type: pass -class tuple(Generic[T]): - def __getitem__(self, x: int) -> T: pass +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Iterable[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass + def __getitem__(self, x: int) -> _Tuple_co: pass class int: pass class str: diff --git a/test-data/unit/fixtures/exception.pyi b/test-data/unit/fixtures/exception.pyi index 963192cc86ab..3e7b6f8cf25d 100644 --- a/test-data/unit/fixtures/exception.pyi +++ b/test-data/unit/fixtures/exception.pyi @@ -1,12 +1,14 @@ import sys -from typing import Generic, TypeVar +from typing import Generic, TypeVar, Iterable, Iterator T = TypeVar('T') class object: def __init__(self): pass class type: pass -class tuple(Generic[T]): +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Iterable[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass def __ge__(self, other: object) -> bool: ... class list: pass class dict: pass diff --git a/test-data/unit/fixtures/f_string.pyi b/test-data/unit/fixtures/f_string.pyi index 328c666b7ece..9e5b75ecf9bd 100644 --- a/test-data/unit/fixtures/f_string.pyi +++ b/test-data/unit/fixtures/f_string.pyi @@ -20,8 +20,6 @@ class list(Iterable[T], Generic[T]): def __init__(self, x: Iterable[T]) -> None: pass def append(self, x: T) -> None: pass -class tuple(Generic[T]): pass - class function: pass class int: def __add__(self, i: int) -> int: pass @@ -36,3 +34,10 @@ class str: class dict: pass + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/fine_grained.pyi b/test-data/unit/fixtures/fine_grained.pyi index e454a27a5ebd..002b7a1926e9 100644 --- a/test-data/unit/fixtures/fine_grained.pyi +++ b/test-data/unit/fixtures/fine_grained.pyi @@ -23,8 +23,14 @@ class str: class float: pass class bytes: pass -class tuple(Generic[T]): pass class function: pass class ellipsis: pass class list(Generic[T]): pass class dict: pass + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/float.pyi b/test-data/unit/fixtures/float.pyi index 9e2d20f04edf..f4451e9c8d5f 100644 --- a/test-data/unit/fixtures/float.pyi +++ b/test-data/unit/fixtures/float.pyi @@ -1,4 +1,4 @@ -from typing import Generic, TypeVar, Any +from typing import TypeVar, Any T = TypeVar('T') class object: @@ -12,8 +12,6 @@ class str: def __rmul__(self, n: int) -> str: ... class bytes: pass - -class tuple(Generic[T]): pass class function: pass class ellipsis: pass @@ -34,3 +32,10 @@ class float: def __rmul__(self, x: float) -> float: ... class dict: pass + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/floatdict.pyi b/test-data/unit/fixtures/floatdict.pyi index 10586218b551..0e0cb928eb01 100644 --- a/test-data/unit/fixtures/floatdict.pyi +++ b/test-data/unit/fixtures/floatdict.pyi @@ -15,8 +15,6 @@ class str: def __rmul__(self, n: int) -> str: ... class bytes: pass - -class tuple(Generic[T]): pass class slice: pass class function: pass @@ -64,3 +62,10 @@ class float: def __rmul__(self, x: float) -> float: ... def __truediv__(self, x: float) -> float: ... def __rtruediv__(self, x: float) -> float: ... + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/for.pyi b/test-data/unit/fixtures/for.pyi index 80c8242c2a5e..b4b6331d883c 100644 --- a/test-data/unit/fixtures/for.pyi +++ b/test-data/unit/fixtures/for.pyi @@ -9,8 +9,6 @@ class object: def __init__(self) -> None: pass class type: pass -class tuple(Generic[t]): - def __iter__(self) -> Iterator[t]: pass class function: pass class ellipsis: pass class bool: pass @@ -22,3 +20,10 @@ class str: # for convenience class list(Iterable[t], Generic[t]): def __iter__(self) -> Iterator[t]: pass class dict: pass + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/function.pyi b/test-data/unit/fixtures/function.pyi index 697d0d919d98..dc9b20a44452 100644 --- a/test-data/unit/fixtures/function.pyi +++ b/test-data/unit/fixtures/function.pyi @@ -6,3 +6,10 @@ class function: pass class int: pass class str: pass class dict: pass + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/isinstance.pyi b/test-data/unit/fixtures/isinstance.pyi index 12cef2035c2b..8282050f1bce 100644 --- a/test-data/unit/fixtures/isinstance.pyi +++ b/test-data/unit/fixtures/isinstance.pyi @@ -9,8 +9,6 @@ class type: def __init__(self, x) -> None: pass def __or__(self, other: type) -> type: pass -class tuple(Generic[T]): pass - class function: pass def isinstance(x: object, t: Union[Type[object], Tuple[Type[object], ...]]) -> bool: pass @@ -28,3 +26,10 @@ class ellipsis: pass NotImplemented = cast(Any, None) class dict: pass + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/isinstance_python3_10.pyi b/test-data/unit/fixtures/isinstance_python3_10.pyi index 0918d10ab1ef..d17df59a6004 100644 --- a/test-data/unit/fixtures/isinstance_python3_10.pyi +++ b/test-data/unit/fixtures/isinstance_python3_10.pyi @@ -11,8 +11,6 @@ class type: def __init__(self, x) -> None: pass def __or__(self, x) -> types.UnionType: pass -class tuple(Generic[T]): pass - class function: pass def isinstance(x: object, t: Union[Type[object], Tuple[Type[object], ...], types.UnionType]) -> bool: pass @@ -29,3 +27,10 @@ class ellipsis: pass NotImplemented = cast(Any, None) class dict: pass + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/isinstancelist.pyi b/test-data/unit/fixtures/isinstancelist.pyi index 2a43606f361a..d14d61e0bbc2 100644 --- a/test-data/unit/fixtures/isinstancelist.pyi +++ b/test-data/unit/fixtures/isinstancelist.pyi @@ -32,7 +32,9 @@ T = TypeVar('T') KT = TypeVar('KT') VT = TypeVar('VT') -class tuple(Generic[T]): +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Iterable[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass def __len__(self) -> int: pass class list(Sequence[T]): diff --git a/test-data/unit/fixtures/len.pyi b/test-data/unit/fixtures/len.pyi index ee39d952701f..7f36f277e5f4 100644 --- a/test-data/unit/fixtures/len.pyi +++ b/test-data/unit/fixtures/len.pyi @@ -10,7 +10,8 @@ class object: class type: def __init__(self, x) -> None: pass -class tuple(Sequence[T]): +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Sequence[_Tuple_co]): def __len__(self) -> int: pass class list(Sequence[T]): pass diff --git a/test-data/unit/fixtures/list.pyi b/test-data/unit/fixtures/list.pyi index 3dcdf18b2faa..fe35ebeeed95 100644 --- a/test-data/unit/fixtures/list.pyi +++ b/test-data/unit/fixtures/list.pyi @@ -26,7 +26,6 @@ class list(Sequence[T]): def append(self, x: T) -> None: pass def extend(self, x: Iterable[T]) -> None: pass -class tuple(Generic[T]): pass class function: pass class int: def __bool__(self) -> bool: pass @@ -39,3 +38,10 @@ class bool(int): pass property = object() # Dummy definition. class dict: pass + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/literal__new__.pyi b/test-data/unit/fixtures/literal__new__.pyi index 971bc39bfff4..bc4b2afc0ae3 100644 --- a/test-data/unit/fixtures/literal__new__.pyi +++ b/test-data/unit/fixtures/literal__new__.pyi @@ -23,3 +23,10 @@ class bool(int): def __new__(cls, __o: _Truthy) -> Literal[True]: pass @overload def __new__(cls, __o: _Falsy) -> Literal[False]: pass + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/module.pyi b/test-data/unit/fixtures/module.pyi index 92f78a42f92f..1c56325c0fda 100644 --- a/test-data/unit/fixtures/module.pyi +++ b/test-data/unit/fixtures/module.pyi @@ -14,7 +14,6 @@ class int: pass class float: pass class str: pass class bool: pass -class tuple(Generic[T]): pass class dict(Generic[T, S]): pass class ellipsis: pass @@ -22,3 +21,10 @@ classmethod = object() staticmethod = object() property = object() def hasattr(x: object, name: str) -> bool: pass + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/module_all.pyi b/test-data/unit/fixtures/module_all.pyi index d6060583b20e..f5fa2b56083a 100644 --- a/test-data/unit/fixtures/module_all.pyi +++ b/test-data/unit/fixtures/module_all.pyi @@ -15,6 +15,12 @@ class list(Generic[_T], Sequence[_T]): def extend(self, x: Sequence[_T]): pass def remove(self, x: _T): pass def __add__(self, rhs: Sequence[_T]) -> list[_T]: pass -class tuple(Generic[_T]): pass class ellipsis: pass class dict: pass + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/notimplemented.pyi b/test-data/unit/fixtures/notimplemented.pyi index c9e58f099477..f197bd6d3c65 100644 --- a/test-data/unit/fixtures/notimplemented.pyi +++ b/test-data/unit/fixtures/notimplemented.pyi @@ -10,7 +10,6 @@ class bool: pass class int: pass class str: pass class dict: pass -class tuple: pass class ellipsis: pass import sys @@ -23,3 +22,10 @@ else: NotImplemented: _NotImplementedType class BaseException: pass + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/object_hashable.pyi b/test-data/unit/fixtures/object_hashable.pyi index 49b17991f01c..32e0bba24222 100644 --- a/test-data/unit/fixtures/object_hashable.pyi +++ b/test-data/unit/fixtures/object_hashable.pyi @@ -6,5 +6,11 @@ class int: ... class float: ... class str: ... class ellipsis: ... -class tuple: ... class dict: pass + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/object_with_init_subclass.pyi b/test-data/unit/fixtures/object_with_init_subclass.pyi index 5e1fdf903294..54cf5d2c4c5a 100644 --- a/test-data/unit/fixtures/object_with_init_subclass.pyi +++ b/test-data/unit/fixtures/object_with_init_subclass.pyi @@ -31,7 +31,6 @@ class bytes(Sequence[int]): def __contains__(self, other: object) -> bool: pass def __getitem__(self, item: int) -> int: pass class bytearray: pass -class tuple(Generic[T]): pass class function: pass class ellipsis: pass @@ -60,3 +59,10 @@ class dict(Mapping[KT, VT]): @overload def get(self, k: KT, default: Union[KT, T]) -> Union[VT, T]: pass def __len__(self) -> int: ... + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/primitives.pyi b/test-data/unit/fixtures/primitives.pyi index 98e604e9e81e..f8000a88e9e7 100644 --- a/test-data/unit/fixtures/primitives.pyi +++ b/test-data/unit/fixtures/primitives.pyi @@ -49,7 +49,9 @@ class memoryview(Sequence[int]): def __iter__(self) -> Iterator[int]: pass def __contains__(self, other: object) -> bool: pass def __getitem__(self, item: int) -> int: pass -class tuple(Generic[T]): +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Iterable[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass def __contains__(self, other: object) -> bool: pass class list(Sequence[T]): def append(self, v: T) -> None: pass diff --git a/test-data/unit/fixtures/property.pyi b/test-data/unit/fixtures/property.pyi index 933868ac9907..2553c861e9d5 100644 --- a/test-data/unit/fixtures/property.pyi +++ b/test-data/unit/fixtures/property.pyi @@ -22,4 +22,9 @@ class bytes: pass class bool: pass class ellipsis: pass -class tuple(typing.Generic[_T]): pass +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/set.pyi b/test-data/unit/fixtures/set.pyi index f757679a95f4..29b81e4865e4 100644 --- a/test-data/unit/fixtures/set.pyi +++ b/test-data/unit/fixtures/set.pyi @@ -9,7 +9,6 @@ class object: def __eq__(self, other): pass class type: pass -class tuple(Generic[T]): pass class function: pass class int: pass @@ -28,3 +27,10 @@ class set(Iterable[T], Generic[T]): def update(self, x: Set[T]) -> None: pass class dict: pass + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/slice.pyi b/test-data/unit/fixtures/slice.pyi index b22a12b5213f..e1ca090fd411 100644 --- a/test-data/unit/fixtures/slice.pyi +++ b/test-data/unit/fixtures/slice.pyi @@ -6,7 +6,6 @@ class object: def __init__(self): pass class type: pass -class tuple(Generic[T]): pass class function: pass class int: pass @@ -17,3 +16,10 @@ class ellipsis: pass class dict: pass class list(Generic[T]): def __getitem__(self, x: slice) -> list[T]: pass + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/type.pyi b/test-data/unit/fixtures/type.pyi index 0d93b2e1fcd6..e8bdf74e3fd0 100644 --- a/test-data/unit/fixtures/type.pyi +++ b/test-data/unit/fixtures/type.pyi @@ -20,7 +20,6 @@ class type: def __ror__(self, other: Union[type, None]) -> type: pass def mro(self) -> List['type']: pass -class tuple(Generic[T]): pass class dict(Generic[T, S]): pass class function: pass class bool: pass @@ -33,3 +32,10 @@ if sys.version_info >= (3, 10): # type: ignore def isinstance(obj: object, class_or_tuple: type | types.UnionType, /) -> bool: ... else: def isinstance(obj: object, class_or_tuple: type, /) -> bool: ... + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/fixtures/union.pyi b/test-data/unit/fixtures/union.pyi index 350e145a6f8f..639ef08da28b 100644 --- a/test-data/unit/fixtures/union.pyi +++ b/test-data/unit/fixtures/union.pyi @@ -10,9 +10,14 @@ class object: class type: pass class function: pass -class tuple(Generic[T]): pass - # We need int for indexing tuples. class int: pass class str: pass # For convenience class dict: pass + +# region ArgumentInferContext +from typing import Mapping, Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass +# endregion ArgumentInferContext diff --git a/test-data/unit/lib-stub/builtins.pyi b/test-data/unit/lib-stub/builtins.pyi index 17d519cc8eea..913335a21d51 100644 --- a/test-data/unit/lib-stub/builtins.pyi +++ b/test-data/unit/lib-stub/builtins.pyi @@ -25,6 +25,10 @@ class ellipsis: pass from typing import Generic, Iterator, Sequence, TypeVar _T = TypeVar('_T') +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass + class list(Generic[_T], Sequence[_T]): def __contains__(self, item: object) -> bool: pass def __getitem__(self, key: int) -> _T: pass diff --git a/test-data/unit/lib-stub/dataclasses.pyi b/test-data/unit/lib-stub/dataclasses.pyi index cf43747757bd..2568a9345c97 100644 --- a/test-data/unit/lib-stub/dataclasses.pyi +++ b/test-data/unit/lib-stub/dataclasses.pyi @@ -50,3 +50,9 @@ def is_dataclass(obj: object) -> TypeGuard[DataclassInstance | type[DataclassIns def replace(__obj: _DataclassT, **changes: Any) -> _DataclassT: ... + + +from typing import Generic, Iterator, TypeVar +_Tuple_co = TypeVar('_Tuple_co', covariant=True) +class tuple(Generic[_Tuple_co]): + def __iter__(self) -> Iterator[_Tuple_co]: pass diff --git a/test-data/unit/outputjson.test b/test-data/unit/outputjson.test index 43649b7b781d..bf36a6c2c246 100644 --- a/test-data/unit/outputjson.test +++ b/test-data/unit/outputjson.test @@ -18,7 +18,7 @@ def foo() -> None: foo(1) [out] -{"file": "main", "line": 5, "column": 0, "message": "Too many arguments for \"foo\"", "hint": null, "code": "call-arg", "severity": "error"} +{"file": "main", "line": 5, "column": 0, "message": "Too many positional arguments for \"foo\"", "hint": null, "code": "call-arg", "severity": "error"} [case testOutputJsonWithHint] # flags: --output=json @@ -41,4 +41,4 @@ bar('42') [out] {"file": "main", "line": 12, "column": 12, "message": "Revealed type is \"Overload(def (), def (x: builtins.int))\"", "hint": null, "code": "misc", "severity": "note"} {"file": "main", "line": 14, "column": 0, "message": "No overload variant of \"foo\" matches argument type \"str\"", "hint": "Possible overload variants:\n def foo() -> None\n def foo(x: int) -> None", "code": "call-overload", "severity": "error"} -{"file": "main", "line": 17, "column": 0, "message": "Too many arguments for \"bar\"", "hint": null, "code": "call-arg", "severity": "error"} +{"file": "main", "line": 17, "column": 0, "message": "Too many positional arguments for \"bar\"", "hint": null, "code": "call-arg", "severity": "error"} diff --git a/test-data/unit/pep561.test b/test-data/unit/pep561.test index 314befa11b94..54f275bbc304 100644 --- a/test-data/unit/pep561.test +++ b/test-data/unit/pep561.test @@ -193,7 +193,7 @@ testNamespacePkgWStubsWithNamespacePackagesFlag.py:8: error: Argument 1 to "bf" import typedpkg_ns.b.bbb as b b.bf("foo", "bar") [out] -testMissingPytypedFlag.py:4: error: Too many arguments for "bf" +testMissingPytypedFlag.py:4: error: Too many positional arguments for "bf" [case testTypedPkgNamespaceRegFromImportTwiceMissing] # pkgs: typedpkg_ns_a diff --git a/test-data/unit/typexport-basic.test b/test-data/unit/typexport-basic.test index 77e7763824d6..346c0913464c 100644 --- a/test-data/unit/typexport-basic.test +++ b/test-data/unit/typexport-basic.test @@ -141,6 +141,9 @@ class type: pass class str: pass class list: pass class dict: pass +from typing import Iterable, Generic, TypeVar +_T_co = TypeVar('_T_co', covariant=True) +class tuple(Generic[_T_co], Iterable[_T_co]): pass [out] OpExpr(3) : builtins.int OpExpr(4) : builtins.float @@ -169,6 +172,9 @@ class function: pass class str: pass class list: pass class dict: pass +from typing import Iterable, Generic, TypeVar +_T_co = TypeVar('_T_co', covariant=True) +class tuple(Generic[_T_co], Iterable[_T_co]): pass [out] ComparisonExpr(3) : builtins.bool ComparisonExpr(4) : builtins.bool From 06f12c00bcfa10fd2fc4f78f52d8fdd4403dafeb Mon Sep 17 00:00:00 2001 From: Randolf Scholz Date: Thu, 25 Dec 2025 12:00:57 +0100 Subject: [PATCH 2/3] fixed but with tuple from Any --- mypy/cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/cache.py b/mypy/cache.py index 832cfb4732ea..4459bcefc385 100644 --- a/mypy/cache.py +++ b/mypy/cache.py @@ -159,7 +159,7 @@ def deserialize(cls, meta: dict[str, Any], data_file: str) -> CacheMeta | None: dep_lines=meta["dep_lines"], dep_hashes=[bytes.fromhex(dep) for dep in meta["dep_hashes"]], interface_hash=bytes.fromhex(meta["interface_hash"]), - error_lines=[tuple(err) for err in meta["error_lines"]], + error_lines=[(*err,) for err in meta["error_lines"]], version_id=meta["version_id"], ignore_all=meta["ignore_all"], plugin_data=meta["plugin_data"], From 16061cf57a2d63d53a8cb9e3ae2f6c8a895a0eab Mon Sep 17 00:00:00 2001 From: Randolf Scholz Date: Thu, 25 Dec 2025 12:06:21 +0100 Subject: [PATCH 3/3] black formatter --- mypy/constraints.py | 3 +-- mypy/tuple_normal_form.py | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/mypy/constraints.py b/mypy/constraints.py index 78c0c0abe651..d4e46cb8266f 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -3,8 +3,7 @@ from __future__ import annotations from collections.abc import Iterable, Sequence -from typing import TYPE_CHECKING, Final, NamedTuple, cast -from typing_extensions import TypeGuard +from typing import TYPE_CHECKING, Final, NamedTuple, TypeGuard, cast import mypy.subtypes import mypy.typeops diff --git a/mypy/tuple_normal_form.py b/mypy/tuple_normal_form.py index 2f8a769318a1..bb5968eaeb50 100644 --- a/mypy/tuple_normal_form.py +++ b/mypy/tuple_normal_form.py @@ -1,9 +1,9 @@ from __future__ import annotations -from collections.abc import Iterable, Sequence +from collections.abc import Callable, Iterable, Sequence from itertools import chain -from typing import TYPE_CHECKING, Callable, NamedTuple, NewType, cast -from typing_extensions import TypeGuard, TypeIs +from typing import TYPE_CHECKING, NamedTuple, NewType, TypeGuard, cast +from typing_extensions import TypeIs from mypy.maptype import map_instance_to_supertype from mypy.nodes import TypeInfo