facebook / facebook/pyrefly

Concrete type should be allowed for constrained TypeVar after narrowing

Open
#3,783 2 comments 3 reactions 0 assignees View on GitHub
narrowing needs-discussion typechecking
Dominant language
Rust
Stars
7k
Forks
516
PR merge metrics
No merged PRs in 30d

Description

### Describe the Bug

Confusing title is confusing, sorry. Essentially, when a TypeVar has constraints, we know that it will solve exactly to one of its constraints (no subtyping), so if we narrow the TypeVar to a concrete type, we should now accept instances of that concrete type where the TypeVar is expected.

Example 1:
```
def f[T: (bytes, str)](x: T) -> T:
if isinstance(x, str):
return "" # this is ok, but pyrefly rejects it
return x
```

Example 2:
```
def f[T: (int, str)](x: T) -> T:
y: T = x
if isinstance(x, str):
y = "" # similarly, this is ok, but pyrefly rejects it
return y
```

Example 3:
An interesting wrinkle involving subtyping between constraints:
```
class A: ...
class B(A): ...

def foo[T: (A, B)](x: T) -> T:
if isinstance(x, A):
return A() # this is not ok! pyrefly correctly rejects it but for incorrect reasons
return x
```

Of the type checkers I tested, mypy is the only one that gets all three cases right:
| checker | example 1 (expects: no error) | example 2 (expects: no error) | example 3 (expects: error) |
| --- | --- | --- | --- |
| mypy | ✅ | ✅ | ✅ |
| pyright | ✅ | ❌ | ❌ |
| pyrefly | ❌ | ❌ | ✅ |
| ty | ❌ | ❌ | ✅ |

### Sandbox Link

_No response_

### (Only applicable for extension issues) IDE Information

_No response_

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.