Summary
We're often too eager to apply intersections when evaluating narrowing constraints created by the is operator. This leads to lots of situations involving NewTypes where ty can incorrectly infer code as being unreachable:
from typing import NewType
class Foo: ...
FooNewType1 = NewType("FooNewType1", Foo)
FooNewType2 = NewType("FooNewType2", Foo)
foo = Foo()
foo1 = FooNewType1(foo)
foo2 = FooNewType2(foo)
if foo1 is foo2: # evaluates to `True` at runtime
reveal_type(foo1) # Never
reveal_type(foo2) # Never
BoolNewType = NewType("BoolNewType", bool)
IntNewType = NewType("IntNewType", int)
StrNewType = NewType("StrNewType", str)
BytesNewType = NewType("BytesNewType", bytes)
true = True # has type `Literal[True]`
fourty_two = 42 # has type `Literal[42]`
some_string = 'some_string' # has type `Literal["some_string"]`
some_bytes = b'some_bytes' # has type `Literal[b"some_bytes"]
if BoolNewType(true) is true: # evaluates to `True` at runtime
reveal_type(true) # Never
if IntNewType(fourty_two) is fourty_two: # evaluates to `True` at runtime
reveal_type(fourty_two) # Never
if StrNewType(some_string) is some_string: # evaluates to `True` at runtime
reveal_type(some_string) # Never
if BytesNewType(some_bytes) is some_bytes: # evaluates to `True` at runtime
reveal_type(some_bytes) # Never
https://play.ty.dev/1ceb245d-d41c-454e-b628-35158ec66414
For comparison, pyrefly appears to have exactly the same bugs as ty here, but pyright/mypy/zuban/pycroscope all seem to avoid this cluster of issues. Multiplay gist: 17be90c733fd42744969a5ecd7214a78
Summary
We're often too eager to apply intersections when evaluating narrowing constraints created by the
isoperator. This leads to lots of situations involving NewTypes where ty can incorrectly infer code as being unreachable:https://play.ty.dev/1ceb245d-d41c-454e-b628-35158ec66414
For comparison, pyrefly appears to have exactly the same bugs as ty here, but pyright/mypy/zuban/pycroscope all seem to avoid this cluster of issues. Multiplay gist: 17be90c733fd42744969a5ecd7214a78