Skip to content

Mypy could not do condition inference #19029

Closed as duplicate
Closed as duplicate

Description

@kaelzhang

Bug Report

To Reproduce

The min sample:

class MyClass:
    a_property: int = 1

    def do_something (
        self,
        source: Optional['MyClass'] = None
    ):
        is_not_none = source is not None

        if is_not_none:
            # My ass even knows that `source` must be a `MyClass` instance here.
            self.a_property = source.a_property

Expected Behavior

It should pass the test

Actual Behavior

Item "None" of "MyClass | None" has no attribute "a_property"  [union-attr]

The only way to avoid this error output is to replace each is_not_none with source is not None. It makes the code silly sometimes, and forces me to revamp the logic just for mypy which is a totally waste of my time.

Your Environment

  • Mypy version used: 1.15.0
  • Mypy command-line flags: mypy foo.py
  • Mypy configuration options from mypy.ini (and other config files): n/a
  • Python version used: 3.12.7

Activity

  1. added and removed
    bugmypy got something wrong
    on May 5, 2025
  2. USEONGEE commented on May 5, 2025

    @USEONGEE

    Hello, I want to solve this problem. Can I?

  3. JelleZijlstra commented on May 5, 2025

    @JelleZijlstra
    Member

    PRs are welcome but this is likely a complicated feature to add.

    Also this issue is probably a duplicate but I haven't looked.

  4. erictraut commented on May 5, 2025

    @erictraut

    I think this is a duplicate of #9229.

    This feature is formally called "aliased conditional expressions". The first time I saw it implemented was in the TypeScript compiler in 2021. I later implemented it in pyright.

    As Jelle said, it's tricky to get this feature right because a static analyzer must prove that there is no statement that can invalidate the condition on any code path between the assignment of the condition variable and its later use for narrowing.

    Consider the following three examples:

    def func1(x: int | None):
        is_int = x is not None
    
        for _ in range(2):
            if is_int: # Safe to narrow x to `int`
                reveal_type(x) # int
    
    def func2(x: int | None):
        is_int = x is not None
    
        for _ in range(2):
            if is_int: # Not safe to narrow x to `int`
                reveal_type(x) # int | None
            x = None
    
    def func3(x: int | None):
        is_int = x is not None
    
        for _ in range(2):
            if is_int: # Not safe to narrow x to `int`
                reveal_type(x) # int | None
            is_int = True
  5. USEONGEE commented on May 5, 2025

    @USEONGEE

    Thanks

  6. kaelzhang commented on May 6, 2025

    @kaelzhang
    Author

    Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions