python / python/mypy

Type narrowing for Optional[T] fails inside an else branch inside a while cycle

Open
#11,947 0 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

bug topic-type-narrowing
Dominant language
Python
Stars
20.6k
Forks
3.3k
PR merge metrics
PR metrics pending

Description

Bug Report

from typing import Optional                                                      
                                                                                 
                                                                                 
class Node:                                                                      
    def __init__(self, value: int):                                      
        self.value = value                                                       
        self.next: Optional[Node] = None                                         
                                                                                 
                                                                                 
def foo(node: Node) -> None:                                                     
    while node.next is not None:                                                 
        if node.value != node.next.value:                                        
            node = node.next                                                     
        else:                                                                    
            node.next = node.next.next   # <-- HERE

Expected Behavior

No mypy errors. Mypy should know that node.next is not None in the else branch.

Actual Behavior

Mypy says error: Item "None" of "Optional[Node]" has no attribute "next" on the line marked by <-- HERE. (With column numbers turned on, the location is the beginning of node.next.next.)

Interestingly, everything is OK if the branches of if are reversed:

def foo(node: Node) -> None:                                                     
    while node.next is not None:                                                 
        if node.value == node.next.value:                                        
            node.next = node.next.next                                           
        else:                                                                    
            node = node.next                                                     

Writing assert node is not None before the problematic line also fixes the error (but seems quite redundant).

Your Environment

  • Mypy version used: mypy 0.930
  • Mypy command-line flags: none
  • Mypy configuration options from mypy.ini (and other config files): default, no config files
  • Python version used: Python 3.10.1
  • Operating system and version: Arch Linux

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.

Research direction

Start by running mypy 0.930 with no configuration on the Optional[Node] example in the issue, then compare the two branch orderings and the assert variant. Trace the type narrowing around the while condition and assignment; done means the original example reports no error without the redundant assert.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
compilers
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.