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

TypeIs[] incorrectly narrows tuple with known length #8554

Closed
InSyncWithFoo opened this issue Jul 26, 2024 · 2 comments
Closed

TypeIs[] incorrectly narrows tuple with known length #8554

InSyncWithFoo opened this issue Jul 26, 2024 · 2 comments
Labels
addressed in next version Issue is fixed and will appear in next published version bug Something isn't working

Comments

@InSyncWithFoo
Copy link
Contributor

Reproducible example (playground):

from typing import TypeIs

def is_tuple_of_strings(v: tuple[int | str, ...]) -> TypeIs[tuple[str, ...]]: ...

def weird_concatenate(args: tuple[int | str, int | str]) -> str:
    reveal_type(args)      # correct:  tuple[int | str, int | str]

    if not is_tuple_of_strings(args):
        reveal_type(args)  # correct:  tuple[int | str, int | str]
        raise TypeError

    reveal_type(args)      # expected: tuple[str, str]
                           # actual:   <subclass of tuple and tuple>

    x, y = args
    return x + y           # expected: fine
                           # actual:   Return type, "int | Unknown | str", is partially unknown

Yet it is able to do a better job when args's length is unknown (playground):

def weird_concatenate(args: tuple[int | str, ...]) -> str:
    reveal_type(args)      # correct:  tuple[int | str, ...]

    if not is_tuple_of_strings(args):
        reveal_type(args)  # correct:  tuple[int | str, ...]
        raise TypeError

    reveal_type(args)      # correct:  tuple[str, ...]

    x, y = args
    return x + y           # correct:  fine

A similar error happens when a TypeVar is used (playground), regardless of length:

def weird_concatenate[T: (int, str)](args: tuple[T, ...]) -> str:
    reveal_type(args)      # correct:  tuple[T, ...]

    if not is_tuple_of_strings(args):
        reveal_type(args)  # expected: tuple[int, ...]
                           # actual:   tuple[T, ...]
        raise TypeError

    reveal_type(args)      # expected: tuple[str, ...]
                           # actual:   <subclass of tuple and tuple>

    x, y = args
    return x + y           # expected: fine
                           # actual:   Type "int* | str*" is incompatible with type "str"
@erictraut
Copy link
Collaborator

erictraut commented Jul 26, 2024

Thanks for the bug report.

I view this as two separate issues. First, pyright is currently producing incorrect narrowed types in this case. The resulting types are overly narrow or just plain wrong. I consider that a bug, and it should be fixed. For comparison, mypy is also producing incorrect narrowed types in this case. I've filed a bug.

I've prepared a fix for the bug. With this fix in place, pyright no longer produces incorrect (too narrow or nonsensical) narrowed types, but it doesn't produce the optimal types that you are hoping to see here. Instead, it refrains from narrowing the type or produces types that are wider than possible. The TypeIs spec allows for some flexibility here, and type checkers are not obliged to always produce "optimal" (narrowest possible) type, so pyright is spec-compliant once this bug fix is in place.

I consider this second part an enhancement request. Producing optimal types in this case is actually quite tricky because the type checker must effectively compute the intersection between two tuple types, and tuple types can be quite complicated. For example, how would one compute the (simplified) intersection of tuple[str, *tuple[int | str, ...], T] and tuple[str | int, ...]? For now, I'm going to reject this enhancement request, but I'm willing to reconsider this decision in the future if there's sufficient demand from pyright users.

erictraut added a commit that referenced this issue Jul 26, 2024
…used in conjunction with `TypeIs`. This addresses #8554.
erictraut added a commit that referenced this issue Jul 26, 2024
…used in conjunction with `TypeIs`. This addresses #8554. (#8559)
@erictraut erictraut added the addressed in next version Issue is fixed and will appear in next published version label Jul 26, 2024
@erictraut
Copy link
Collaborator

This is addressed in pyright 1.1.374.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
addressed in next version Issue is fixed and will appear in next published version bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants