8000 type guarded value is same type as `typing.TypeGuard[T]`? · Issue #10899 · python/mypy · GitHub
[go: up one dir, main page]

Skip to content
type guarded value is same type as typing.TypeGuard[T]? #10899
Closed
@A5rocks

Description

@A5rocks

Bug Report

Okay so, let's say for a second you have this code:

from typing_extensions import TypeGuard

def type_guard_function(a: object) -> TypeGuard[int]:
    return isinstance(a, int)

reveal_type(type_guard_function)

n: object = 5
reveal_type(n)

if type_guard_function(n):
    reveal_type(n)
else:
    reveal_type(n)

Now then, it turns out that in this bit:

if type_guard_function(n):
    reveal_type(n)

mypy actually says n is a TypeGuard! If you look at ConditionalTypeBinder#frames during the reveal_type, you'll note a: {('Var', <mypy.nodes.Var object at 0x7f7a76e32580>): TypeGuard(builtins.int)}.

ConditionalTypeBinder stores types. To prove this to yourself, add a n = '5' after n: object = 5. You'll see a frame now looks like: {('Var', <mypy.nodes.Var object at 0x7f7a76e32580>): builtins.str} (note that since it's the same variable, it's the same memory address :P)...

Anyways, this seems completely off, because in types.py's TypeStrVisitor, you have this:

    def visit_type_guard_type(self, t: TypeGuardType) -> str:
        return 'TypeGuard[{}]'.format(t.type_guard.accept(self))

Here are the relevant lines to the above:

  1. Type narrowing working around TypeGuardType:

mypy/mypy/checkexpr.py

Lines 4188 to 4190 in 7edcead

if isinstance(restriction, TypeGuardType): # type: ignore[misc]
# A type guard forces the new type even if it doesn't overlap the old.
return restriction.type_guard

This is what causes reveal_type(n) in the if statement to be int.

  1. The implementation that adds TypeGuardType to the value in the first place:

return {expr: TypeGuardType(node.callee.type_guard)}, {}

  1. TypeStrVisitor#visit_type_guard_type

mypy/mypy/types.py

Lines 2197 to 2198 in 7edcead

def visit_type_guard_type(self, t: TypeGuardType) -> str:
return 'TypeGuard[{}]'.format(t.type_guard.accept(self))

Expected Behavior

This doesn't happen. TypeGuardType is only for TypeGuard[T]. Instead, there's another way to signal to type narrowing that a value became the way it is because of a type guard (necessary because of differences in behavior).

Actual Behavior

This does happen. Chaos ensues, causing things like #10647 and #10665. (confirmed this by removing relevant line number 2 and testing both -- no crashes. However, I did this as described in #10671 (comment) and well, the tests show the problem themselves.)

Your Environment

The source code.

I was redirected here by the contributing document, as this is a moderate (I think?) effort change, and I have no clue what exactly should change. Like, should type guarded values use their own ProperType? Should there be a bool flag somewhere? No clue.

Full disclaimer that I probably messed a few things up in my explanation, and that I may have misspoken a few terms. Hopefully I'm wrong and this isn't actually an issue 😰 .

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugmypy got something wrong

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      0