Skip to content

Add a strictness flag that disables the bivariant behavior of TypeGuard #11230

Closed

Description

@KotlinIsland
from typing import TypeGuard


def is_str_list(val: list[object]) -> TypeGuard[list[str]]:
    return all(isinstance(x, str) for x in val)

l1: list[object] = []
l2 = l1

if is_str_list(l1):
    l2.append(1)
    impostor: str = l1[0]  # SUS ALERT!!!! 😳😳😳😳😳😳😳

I can't stand bivariance, and I believe it should always be an opt-in behavior. I don't know any other place in python typing where bivariance exists (except for an Any generic parameter).

Please add a strictness flag that enforces standard python typing variance rules on TypeGuard.

Activity

  1. JelleZijlstra commented on Mar 19, 2022

    @JelleZijlstra
    Member

    Can you clarify what error you'd want to see exactly?

  2. KotlinIsland commented on Mar 19, 2022

    @KotlinIsland
    ContributorAuthor
    from typing import TypeGuard
    
    def is_str_list(val: list[object]) -> TypeGuard[list[str]]:  # E: list[str] is not a subtype of list[object]
        ...
  3. erictraut commented on Mar 19, 2022

    @erictraut

    What value would that error message provide? Who would use it other than you? And if you saw that error message, what would you do to fix it?

    You can choose not to use this type of TypeGuard in your code if you don't like it.

  4. DetachHead commented on Mar 20, 2022

    @DetachHead
    Contributor

    i believe this flag would be useful, however the broader issue is that TypeGuard doesn't enforce that the narrowed type is a subtype in the first place.

    from the PEP:

    The return type of a user-defined type guard function will normally refer to a type that is strictly “narrower” than the type of the first argument (that is, it’s a more specific type that can be assigned to the more general type). However, it is not required that the return type be strictly narrower. This allows for cases like the example above where List[str] is not assignable to List[object].

    wouldn't the problem be much better solved by simply making TypeGuard take variance into account? that way, you can enforce that the narrowed type is actually narrower

    What value would that error message provide? Who would use it other than you? And if you saw that error message, what would you do to fix it?

    but couldn't you say the same thing about other scenarios where variance is taken into account?

    def foo(value: list[object]) -> None: ...
    
    bar: list[int]
    
    foo(bar) # error: Argument 1 to "foo" has incompatible type "List[int]"; expected "List[object]"

    in this case, you get some helpful information as well:

    main.py:5: note: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance
    main.py:5: note: Consider using "Sequence" instead, which is covariant
    

    how is this any different?

  5. KotlinIsland commented on Mar 20, 2022

    @KotlinIsland
    ContributorAuthor

    What value would that error message provide? ... You can choose not to use this type of TypeGuard in your code if you don't like it.

    I understand the desire for unsafe narrowing, the issue I have is that it's extremely inconsistent with the rest of the typing landscape. The example from the pep: list[object] to list[int] is very obviously unsafe, but once type aliases, contra-variance and refactoring (and I'm sure other things) are taken into account, it would would be quite easy to accidentally write/introduce unsafe code.

    Ideally I would want a TypeGuard and an UnsafeTypeGuard. But until such option is available I much prefer the assurance that the code I write is safe.

  6. erictraut commented on Mar 20, 2022

    @erictraut

    There has been discussion in the typing-sig about a StrictTypeGuard. I've even implemented it in pyright as a proof of concept, but there hasn't been enough interest to proceed with a PEP at this time.

    But until such option is available I much prefer the assurance that the code I write is safe.

    Then don't use the current TypeGuard feature in your code — or at least avoid using it in a manner that you don't like. I see no value in adding an optional error in mypy to tell you not to use it.

  7. KotlinIsland commented on Apr 6, 2024

    @KotlinIsland
    ContributorAuthor

    Then don't use the current TypeGuard feature in your code — or at least avoid using it in a manner that you don't like.

    The issue is that there is no way to know if the usage is safe or not, withought performing manual type checking.

    This has been addressed in basedmypy:

    def is_str_list(val: list[object]) -> val is list[str]:  # error: A type-guard's type must be assignable to its parameter's type. (guard has type "list[str]", parameter has type "list[object]")  [typeguard-subtype]
        return all(isinstance(x, str) for x in val)

    playground

    So I'm going to close it.

  8. JelleZijlstra commented on Apr 6, 2024

    @JelleZijlstra
    Member

    FYI, TypeIs (added by PEP 742 and implemented in the next release of mypy) also requires that the output type is a subtype of the input type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions