Skip to content

Commit 4ccf216

Browse files
Fix cross-variable type-narrowing example (#17488)
From [<i>Type narrowing</i> &sect; <i>Limitations</i>](https://github.com/python/mypy/blob/606971807fad1de26ebc575d327d4c1c33f71c0e/docs/source/type_narrowing.rst#limitations): ```python def f(a: str | None, b: str | None) -> str: if a is not None or b is not None: return a or b # Incompatible return value type (got "str | None", expected "str") return 'spam' ``` A trivial counter-example is `f('', None)`, which returns `None`. Ironically, this somewhat makes Mypy's diagnostic "correct". I propose that `str` be replaced with a custom class `C` whose `__bool__()` is not defined (does it have to be `@final` too?): ```python class C: pass def f(a: C | None, b: C | None) -> C: if a is not None or b is not None: return a or b # Incompatible return value type (got "C | None", expected "C") return C() ```
1 parent 1ea8676 commit 4ccf216

File tree

1 file changed

+10
-6
lines changed

1 file changed

+10
-6
lines changed

docs/source/type_narrowing.rst

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -368,14 +368,18 @@ Limitations
368368
Mypy's analysis is limited to individual symbols and it will not track
369369
relationships between symbols. For example, in the following code
370370
it's easy to deduce that if :code:`a` is None then :code:`b` must not be,
371-
therefore :code:`a or b` will always be a string, but Mypy will not be able to tell that:
371+
therefore :code:`a or b` will always be an instance of :code:`C`,
372+
but Mypy will not be able to tell that:
372373

373374
.. code-block:: python
374375
375-
def f(a: str | None, b: str | None) -> str:
376+
class C:
377+
pass
378+
379+
def f(a: C | None, b: C | None) -> C:
376380
if a is not None or b is not None:
377-
return a or b # Incompatible return value type (got "str | None", expected "str")
378-
return 'spam'
381+
return a or b # Incompatible return value type (got "C | None", expected "C")
382+
return C()
379383
380384
Tracking these sort of cross-variable conditions in a type checker would add significant complexity
381385
and performance overhead.
@@ -385,9 +389,9 @@ or rewrite the function to be slightly more verbose:
385389

386390
.. code-block:: python
387391
388-
def f(a: str | None, b: str | None) -> str:
392+
def f(a: C | None, b: C | None) -> C:
389393
if a is not None:
390394
return a
391395
elif b is not None:
392396
return b
393-
return 'spam'
397+
return C()

0 commit comments

Comments
 (0)