Concrete type should be allowed for constrained TypeVar after narrowing
- 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
Assessment
This issue has not been assessed yet.