I found Jukka’s referenced comment to be valuable, so here’s a direct link: https://github.com/python/mypy/issues/2008#issuecomment-296213676 Eg, I believe pyright’s narrowing to Animal corresponds in part to Jukka’s Proposal 2 (or Proposal 4), which he mentions is TypeScript’s behaviour: when narrowing a Union, narrow to a member of the Union.

My view on the invariance issue is the most natural flow is to start with:
def g(x: Foo[Union[int, str]]): ...

def f() -> None:
    y = 1
    a = Foo(y)
Then get an error (with a very clear message / suggestion) and add:
def f() -> None:
    y = 1
    a: Foo[Union[int, str]] = Foo(y)
Which is fine in the presence of type narrowing, because obviously we shouldn’t narrow to an incompatible type. (It’s also my opinion that this is the type annotation that best helps describe what’s happening)

I don’t know that I’d be so quick to write off always narrowing (modulo Any), which I believe corresponds to Jukka’s Proposal 3. I’d be curious to know more about the significant number of false positives you’d expect; that seems contra my intuition and the results on the sample corpus Jukka described. It also feels wrong to mandate that all future type checkers can’t narrow in part because a majority of our current ones do not. It feels like if a type checker can prove something about a value, it should be fair game (and that as long as users can see that proof is possible, we’ll get bug reports).

Generally speaking, it’s surprising to me to make a usability argument for a distinction that most end users wouldn’t notice or weren’t making with intention. But maybe this is a case where if the behaviour is PEP-ed users will no longer find it surprising.

On Tue, 8 Sep 2020 at 09:28, dimvar--- via Typing-sig <typing-sig@python.org> wrote:
I also prefer form (1) to respect the declared type and not use the rhs type. Keeping the declared type can avoid a spurious warning in cases where type invariance matters, e.g., with generics.

T = TypeVar('T')

class Foo(Generic[T]):
    def __init__(self, x: T) -> None:
        self.attr = x

def g(x: Foo[Union[int, str]]):
    return x

def f(x: int):
    y: Union[int, str] = 1
    a = Foo(y)
    g(a) # no warning

    y = 2
    b = Foo(y)
    g(b) # warning
Typing-sig mailing list -- typing-sig@python.org
To unsubscribe send an email to typing-sig-leave@python.org
Member address: hauntsaninja@gmail.com