Bound TypeVar solved to a type outside its bound

Open
#4,937 0 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
55/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Active
Tech stack
python, rust

Research direction

Start by running the provided reproduction in t.py or the linked Pyrefly sandbox and confirm that pyrefly reveals Box[int | None] while the other checkers reveal Box[Any]. Trace overload resolution and TypeVar bound handling for this call, then add a regression test showing that the bounded overload is rejected and the untyped overload produces Box[Any].

Written by the indexing model from the issue text.

Description

quansight
Describe the Bug
from collections.abc import Mapping
from typing import Any, Generic, overload, reveal_type

from typing_extensions import TypeVar


S1 = TypeVar("S1", bound=int, default=Any)
S2 = TypeVar("S2", bound=int)


class Box(Generic[S1]):
    @overload
    def map(self, func: Mapping[S1, S2]) -> "Box[S2]": ...
    @overload
    def map(self, func: Mapping[Any, Any]) -> "Box": ...
    def map(self, func: Any) -> Any: ...


b: Box[Any] = Box()

# The dict literal's value type is `int | None`. `None` is not assignable to
# `int` (the bound on S2), so the first overload should not apply, and this
# call should fall through to the second (untyped) overload, giving `Box[Any]`.
reveal_type(b.map({1: 10, 0: 20, None: None}))  # revealed: Box[int | None]

The revealed type here is Box[int | None, even though None isn't part of the S2 bound (i.e. int)

mypy / pyright / ty all reveal Box[Any]

(scratch) mgorelli@marcoslaptop:~/scratch$ mypy t.py
t.py:23: note: Revealed type is "t.Box[Any]"
Success: no issues found in 1 source file
(scratch) mgorelli@marcoslaptop:~/scratch$ pyright t.py
/home/mgorelli/scratch/t.py
  /home/mgorelli/scratch/t.py:23:13 - information: Type of "b.map({ 1: 10,0: 20,None: None })" is "Box[Any]"
0 errors, 0 warnings, 1 information
(scratch) mgorelli@marcoslaptop:~/scratch$ ty check t.py
info[revealed-type]: Revealed type
  --> t.py:23:13
   |
23 | reveal_type(b.map({1: 10, 0: 20, None: None}))
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `Box[Any]`

Found 1 diagnostic
(scratch) mgorelli@marcoslaptop:~/scratch$ pyrefly check t.py
 INFO revealed type: Box[int | None] [reveal-type]
  --> t.py:23:12
   |
23 | reveal_type(b.map({1: 10, 0: 20, None: None}))
   |            -----------------------------------
 INFO 0 errors

Spotted in pandas-stubs

Sandbox Link

https://pyrefly.org/sandbox/?project=v2.pZXRaoNAEEV_ZYgPKqQafRQCpV9QsG8xVBONCuJK1JBS-u-9M66p0lQoIaDJZnbcud45s4jMPyiiOaHZcIerN3guQ2rY-Z5du6xmxW5s0VzkwKgOPRSuV6xoFXrRak0H1dfpFq97zWZL-qrb4sl47aE_j4b6k2ixz8iBF3W19El3obcfrf88HnrS0Umj25l7MxgFwC6w0t-Ly6MV8u3wC4Ylx3H-nUyUw2WSbpbq3l6umaNx15H8OQRcGyfcQwwuU-o26K3IKMWUARVlgJmtZga_FgZHDI00aGKHYrnzOhzJMwgW5ZlBHdhuSHBMVoekIjDB86FvgyOKePFUnltgXQtAbaH6Kh1SNU2FYuE3BJZoQgNDrarGkBN_74qz6vMCz5JkbXZUCLf6ms-a2hNr5eWFDRiPNceQYWI36-CwbJ9eQN5mTZuAfNwESnL9sm0iQxs0SwftfnRAy_7udINehwXSTYNWASLyHpMURsYBDAzRjIqua9rAdcft6py7We2m6ti6sx0uMdSSS1LKfJ6nIyVkapH2EcSYA2HMJcCYzBeJeErqDwTJCMC6JMcfizD5Bg

(Only applicable for extension issues) IDE Information

No response

Dominant language
Rust
Stars
7k
Forks
521
PR merge metrics
No merged PRs in 30d

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from facebook/pyrefly

All issues in facebook/pyrefly

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.