Skip to content

isinstance check in function call is not recognized in call-site scope #4220

Description

@wbadart

Describe the bug
Hey team- sorry, I wasn't sure how best to phrase the title. Essentially, when I have a subclass that adds properties to a parent, an if isinstance(thing, Child): ... check makes pyright narrow the type and accept me accessing the extra properties in the body of the if. However, I've noticed that if the isinstance check happens inside a function call, pyright no longer recognizes that I should be able to access the extra properties.

To Reproduce

class Empty:
    def has_foo(self) -> bool:
        return isinstance(self, HasFoo)


class HasFoo(Empty):
    foo = None


def go(x: Empty) -> None:
    if isinstance(x, HasFoo):
        print(x.foo)  # ✅ 

    if x.has_foo():
        print(x.foo)  # ❌ Cannot access member "foo" for type "Empty"    Member "foo" is unknown

Expected behavior
I expected that abstracting the instance check into a function would not prevent the gained type information from propagating to the calling scope (that being said, I can imagine how this might be a can of worms; mypy exhibits the same behavior).

VS Code extension or command-line

$ ~/.local/share/nvim/mason/bin/pyright --version
pyright 1.1.280
$ nvim --version
NVIM v0.9.0-dev-1279-g26c653718-dirty
Build type: Release
LuaJIT 2.1.0-beta3
Features: +acl +iconv +tui

Additional context
Please let me know if this is the intended behavior! (And if so, would greatly appreciate any input on workarounds [if there is in fact anything to be done short of bringing isinstance back into the calling scope].)

I also wanted to ask a tangentially related question: would it be possible to implement some kind of narrowing with hasattr when the second argument is a Literal? I.e., making this implementation of go from above acceptable:

def go(x: Empty) -> None:
    if hasattr(x, "foo"):
        print(x.foo)  # ❌ Cannot access member "foo" for type "Empty"    Member "foo" is unknown

Many thanks!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions