facebook / facebook/pyrefly

Unpacked iterables are not typed correctly

Open
#3,894 11 comments 0 reactions 0 assignees View on GitHub
needs-discussion typechecking
Dominant language
Rust
Stars
7k
Forks
516
PR merge metrics
No merged PRs in 30d

Description

### Describe the Bug

I'm trying to pop the first element in a `tuple[T]` in a type-safe way, i.e.:
```py
from typing import assert_type
from typing import reveal_type
from typing import TypeVar, TypeAlias, overload

T = TypeVar("T")
TupleOrValue: TypeAlias = tuple[T, ...] | T

@overload
def _split_tuple_or_value(tuple_or_value: None) -> tuple[None, None]:
"""No value."""
...

@overload
def _split_tuple_or_value(tuple_or_value: T | tuple[T]) -> tuple[T, None]:
"""Single value."""
...

@overload
def _split_tuple_or_value(tuple_or_value: tuple[T, T]) -> tuple[T, T]:
"""Pair of values."""
...

@overload
def _split_tuple_or_value(
tuple_or_value: tuple[T, ...],
) -> tuple[T, tuple[T, ...]]:
"""More than two values."""
...

def _split_tuple_or_value(
tuple_or_value: TupleOrValue[T],
) -> tuple[T, TupleOrValue[T] | None]:
"""Split the given `TupleOrValue[T]` into its head and tail.

E.g. in the following cases:
* `a, b = _split_tuple_or_value((1, 2, 3))`: `a=1`, `b=(2, 3)`,
* `a, b = _split_tuple_or_value((1, 2))`: `a=1`, `b=2`,
* `a, b = _split_tuple_or_value((1,))`: `a=1`, `b=None`,
* `a, b = _split_tuple_or_value(1)`: `a=1`, `b=None`,

Args:
tuple_or_value: The `TupleOrValue[T]` to split.

Returns:
A `tuple` consisting of the first element of the input and the remaining
elements as a `TupleOrValue[T]`.
"""
if not isinstance(tuple_or_value, tuple):
head, tail = tuple_or_value, None
else:
head, *tail = tuple_or_value
tail = tuple(tail)
if len(tail) < 2:
tail, *_ = tail + (None,)
assert_type(head, T)
assert_type(tail, TupleOrValue[T] |None)
return head, tail

def test(tov: TupleOrValue[int]):
head, tail = _split_tuple_or_value(tov)
assert_type(head, int)
assert_type(tail, TupleOrValue[int])
```
The last two `assert_type` statements fail with:
```
ERROR sandbox.py:66:16-27: assert_type(int | tuple[int, ...], int) failed [assert-type]
ERROR sandbox.py:67:16-41: assert_type(None, int | tuple[int, ...]) failed [assert-type]
```
which is odd, since:
* All `@overload`s have the first return value as type `T`,
* The `assert_type` statements at the end of `_split_tuple_or_value` don't fail.

### Sandbox Link

https://pyrefly.org/sandbox/?project=N4IgZglgNgpgziAXKOBDAdgEwEYHsAeAdAA4CeS4ATrgLYAEALqcROgOZ0Q3G6UN2o4cGHwD6TYjAA66MNXoTWHLjz51KMAG4xUUcc2mz5jZks7de-ACoGAaqkoAaOjckBBKBEHPc2ylFxUTBkZKzoAXhc7BwAKKRAreIBKUIBXYlgAeUp7KFSYRCj3T0EIxnTYAG0rZ0I6gF06AB8XEPQZAAFfEQCgmUwYMDpROAyIBnEKmFFeUU1dfJiGKZnKOYWCugA5XHQYJLoAWgA%2BcoyYSp295yuYesQZOjp4l5Adunm8mEJXl-Qnuo-drArp%2BXrBLCDYajTwTZbnVbrL5LFazT75QphFrwqpWeoHE5nXE3XZ3B7-Z4gV4AZSUsA%2BGx%2BVOZjzogLanW6-kCEIGQxGYzhqLW6JgKIRaI2hRxFxqLnxR1OMuqzjx5KevxAAAVUBBKHRcENRXAmb8KezgZywTz%2BlCBbDJhKRRs4hSnjLEaLpVMVWyGo4ZASlT65cq5YD6vdWZqALK8GCMAAWGEYAHdcAyvibNayLW0%2BdDBY7YJ6Xaz3cKkRiXFNsrl8tV6gH0EGibLVbWchtG81tqSoxTNdTBUmE2wINp-gADKyd%2Buy%2BpTzjoBgZ8ZwOiJnSYARYRi6qBA1kAUUIbEIy9HdDAuCgAVTZgAxoJ4Oq6AAqOhT1DObBle3jMW0ySsiMQAIzOAATM4ADMSRJFOhTfuEYFTs4U7YOEMTQXQcFoayn7fr%2B-4woBHogYs4FQfBiFfqgKFoV%2BmGQfhFKET%2BdB-pEAFCk6VZilRNFIfRqHoZhtysU87HEdxpG8SWFFimBCHCQxYnhBJzasm4lBsHAb4VnxXouFuX6zucdbdniS6rnQclHhSABKMDLJQ6D6eWdBuF%2BMpLo%2BuxwBAcAMGYhpXpAlDBXQMCwDQMArgaQwMKZrDEKk-AYDuyUJhoNC6ugSieTFMBxSuG6lKgZlzlZi5AhqLJUhSEBDOguD8EFrDBRgj5iuRzpfM4MpJAZm7boNB5lH1-EknsrIxcII1bkEzjvgwE2RFNoqeWt0CTVMSwHikbqcEMsDoAd0AHAAPHQkEje6B4raIk0TQA1HQMS3I4R1PIIwhiBIYpLZgqo-QIQgiHCBgXVAHYWV2Xw9i0txgxorn-MD43QPmUIMPADBLL4mLVYjrAMPib6Y-uu2yUWm0uqumhg39kP6JIMRU2TzMQwD0M7bDNbw-OlRk-iICOCAZAaGAUCkIQq40FAFAAMR0FqpDS7LdmZXg%2BB0P5sgQGwqSUKgIW7ECqvUjACaJgwDDEPpAD0TtS4MsuELwbBO-FTuYLgj5wE7BuQMbpvm%2BgTvXrwAjzNAqDYPSIdGybZsQLsBrEBH2boGQyW7IcfiBRnkTxDBhBgZBfyVCI1CRfUMipAVFh8DAmCHJgeowI%2BIXaGUADkRutRo-cyK1DCHBoACOqRd%2B3ADWMCkIcqCPj1QgD6mDjoKP6AgAAvhLq%2B9zAABi0AwBQaBYLrJDkPvQA

### (Only applicable for extension issues) IDE Information

_No response_

Contributor guide

Open the contributing guide

Research direction

Start by reproducing the reported failure from the linked Pyrefly sandbox and inspect how the overload call is inferred for TupleOrValue[int]. Trace the relevant type-checking behavior, then verify that the final assert_type statements infer int and TupleOrValue[int] while the existing assertions continue to pass.

Written by the indexing model from the issue text.

Assessment

Tech stack
python, rust
Domain
compilers, devtools
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
50/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.