Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Better support for variadic calls and indexing #16131

Merged
merged 6 commits into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Handle more cases in calls
  • Loading branch information
ilevkivskyi committed Sep 16, 2023
commit 43a6b5191365c54ff9e74fd8e65a59a8e122d2f3
39 changes: 24 additions & 15 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -2403,7 +2403,7 @@ def check_argument_types(
]
actual_kinds = [nodes.ARG_STAR] + [nodes.ARG_POS] * (len(actuals) - 1)

# TODO: can we really assert this? What if formal is just plain Unpack[Ts]?
# If we got here, the callee was previously inferred to have a suffix.
assert isinstance(orig_callee_arg_type, UnpackType)
assert isinstance(orig_callee_arg_type.type, ProperType) and isinstance(
orig_callee_arg_type.type, TupleType
Expand All @@ -2429,22 +2429,29 @@ def check_argument_types(
inner_unpack = unpacked_type.items[inner_unpack_index]
assert isinstance(inner_unpack, UnpackType)
inner_unpacked_type = get_proper_type(inner_unpack.type)
# We assume heterogenous 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)
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:
# TODO: Any and Never can appear in Unpack (as a result of user error),
# fail gracefully here and elsewhere (and/or normalize them away).
assert isinstance(unpacked_type, Instance)
assert unpacked_type.type.fullname == "builtins.tuple"
callee_arg_types = [unpacked_type.args[0]] * len(actuals)
Expand All @@ -2456,8 +2463,10 @@ def check_argument_types(
assert len(actual_types) == len(actuals) == len(actual_kinds)

if len(callee_arg_types) != len(actual_types):
# TODO: Improve error message
self.chk.fail("Invalid number of arguments", context)
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

assert len(callee_arg_types) == len(actual_types)
Expand Down
31 changes: 22 additions & 9 deletions mypy/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,25 +137,38 @@ def infer_constraints_for_callable(
unpack_type = callee.arg_types[i]
assert isinstance(unpack_type, UnpackType)

# In this case we are binding all of the actuals to *args
# 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

actual_types.append(
mapper.expand_actual_type(
actual_arg_type,
arg_kinds[actual],
callee.arg_names[i],
callee.arg_kinds[i],
)
expanded_actual = mapper.expand_actual_type(
actual_arg_type, arg_kinds[actual], callee.arg_names[i], callee.arg_kinds[i]
)

unpacked_type = get_proper_type(unpack_type.type)
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]))
)

if isinstance(unpacked_type, TypeVarTupleType):
constraints.append(
Constraint(
Expand Down
28 changes: 22 additions & 6 deletions test-data/unit/check-typevar-tuple.test
Original file line number Diff line number Diff line change
Expand Up @@ -366,13 +366,21 @@ from typing_extensions import TypeVarTuple, Unpack

Ts = TypeVarTuple("Ts")

# TODO: add less trivial tests with prefix/suffix etc.
# TODO: add tests that call with a type var tuple instead of just args.
def args_to_tuple(*args: Unpack[Ts]) -> Tuple[Unpack[Ts]]:
reveal_type(args) # N: Revealed type is "Tuple[Unpack[Ts`-1]]"
return args
reveal_type(args_to_tuple(1, *args)) # N: Revealed type is "Tuple[Literal[1]?, Unpack[Ts`-1]]"
reveal_type(args_to_tuple(*args, 'a')) # N: Revealed type is "Tuple[Unpack[Ts`-1], Literal['a']?]"
reveal_type(args_to_tuple(1, *args, 'a')) # N: Revealed type is "Tuple[Literal[1]?, Unpack[Ts`-1], Literal['a']?]"
if int():
return args
else:
return args_to_tuple(*args)

reveal_type(args_to_tuple(1, 'a')) # N: Revealed type is "Tuple[Literal[1]?, Literal['a']?]"
vt: Tuple[int, ...]
reveal_type(args_to_tuple(1, *vt)) # N: Revealed type is "Tuple[Literal[1]?, Unpack[builtins.tuple[builtins.int, ...]]]"
reveal_type(args_to_tuple(*vt, 'a')) # N: Revealed type is "Tuple[Unpack[builtins.tuple[builtins.int, ...]], Literal['a']?]"
reveal_type(args_to_tuple(1, *vt, 'a')) # N: Revealed type is "Tuple[Literal[1]?, Unpack[builtins.tuple[builtins.int, ...]], Literal['a']?]"
[builtins fixtures/tuple.pyi]

[case testTypeVarTuplePep646TypeVarStarArgs]
Expand All @@ -381,8 +389,17 @@ from typing_extensions import TypeVarTuple, Unpack

Ts = TypeVarTuple("Ts")

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"
new_args = (True, "foo", *args, 5)
with_prefix_suffix(*new_args)
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']?]"
return args

reveal_type(with_prefix_suffix(True, "bar", "foo", 5)) # N: Revealed type is "Tuple[builtins.bool, builtins.str, Literal['foo']?, builtins.int]"
Expand All @@ -395,8 +412,7 @@ t = (True, "bar", "foo", 5)
reveal_type(with_prefix_suffix(*t)) # N: Revealed type is "Tuple[builtins.bool, builtins.str, builtins.str, builtins.int]"
reveal_type(with_prefix_suffix(True, *("bar", "foo"), 5)) # N: Revealed type is "Tuple[builtins.bool, builtins.str, Literal['foo']?, builtins.int]"

# TODO: handle list case
#reveal_type(with_prefix_suffix(True, "bar", *["foo1", "foo2"], 5))
reveal_type(with_prefix_suffix(True, "bar", *["foo1", "foo2"], 5)) # N: Revealed type is "Tuple[builtins.bool, builtins.str, Unpack[builtins.tuple[builtins.str, ...]], builtins.int]"

bad_t = (True, "bar")
with_prefix_suffix(*bad_t) # E: Too few arguments for "with_prefix_suffix"
Expand Down Expand Up @@ -908,7 +924,7 @@ def cons(
return wrapped

def star(f: Callable[[X], Y]) -> Callable[[Unpack[Tuple[X, ...]]], Tuple[Y, ...]]:
def wrapped(*xs: X):
def wrapped(*xs: X) -> Tuple[Y, ...]:
if not xs:
return nil()
return cons(f, star(f))(*xs)
Expand Down