python / python/mypy

mypy doesn't narrow `Final` objects in nested contexts

Open
#19,080 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

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

Description

Bug Report

I have a global constant I've marked with Final. In a class definition, I use this constant to swap out implementations of a method. While inside a type guarding block, but outside the method definition, the type of the Final object is narrowed, but inside of the method it is not narrowed.

To Reproduce

https://mypy-play.net/?mypy=latest&python=3.12&gist=2d4c97cffea1dd6710bf5cc7283f7290

from typing import *

def _init() -> None | str:
    return None

RESOLVE_X: Final = _init()

class Example:
    
    if RESOLVE_X is not None:
        
        reveal_type(RESOLVE_X)  # note: Revealed type is "builtins.str"
        
        def __str__(self) -> str:
            return RESOLVE_X  # error: Incompatible return value type (got "str | None", expected "str")  [return-value]
            
    else:
        def __str__(self) -> str:
            return "example"

Expected Behavior

Inside the __str__ definition in the if RESOLVE_X is not None: block, RESOLVE_X should stay narrowed as it cannot be reassigned and have its type re-widened in the future.

Actual Behavior

main.py:12: note: Revealed type is "builtins.str"
main.py:15: error: Incompatible return value type (got "str | None", expected "str")  [return-value]
Found 1 error in 1 file (checked 1 source file)

Your Environment

Mypy version 1.15.0.
Python 3.12.

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 with the mypy-play reproduction linked in the issue and run it against the reported Python 3.12 and mypy 1.15.0 environment. Trace how the type guard for the module-level Final is handled in the class body versus the nested str definition. Done means the revealed narrowing is preserved inside str and the example passes without the return-value error.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.