Files
ruff/crates/ty_python_semantic/resources/mdtest/narrow/hasattr.md
Alex Waygood 5bf5f3682a [ty] Add tests for else branches of hasattr() narrowing (#18067)
## Summary

This addresses @sharkdp's post-merge review in
https://github.com/astral-sh/ruff/pull/18053#discussion_r2086190617

## Test Plan

`cargo test -p ty_python_semantic`
2025-05-13 09:57:53 -04:00

1.0 KiB

Narrowing using hasattr()

The builtin function hasattr() can be used to narrow nominal and structural types. This is accomplished using an intersection with a synthesized protocol:

from typing import final

class Foo: ...

@final
class Bar: ...

def f(x: Foo):
    if hasattr(x, "spam"):
        reveal_type(x)  # revealed: Foo & <Protocol with members 'spam'>
        reveal_type(x.spam)  # revealed: object
    else:
        reveal_type(x)  # revealed: Foo & ~<Protocol with members 'spam'>

        # TODO: should error and reveal `Unknown`
        reveal_type(x.spam)  # revealed: @Todo(map_with_boundness: intersections with negative contributions)

    if hasattr(x, "not-an-identifier"):
        reveal_type(x)  # revealed: Foo
    else:
        reveal_type(x)  # revealed: Foo

def y(x: Bar):
    if hasattr(x, "spam"):
        reveal_type(x)  # revealed: Never
        reveal_type(x.spam)  # revealed: Never
    else:
        reveal_type(x)  # revealed: Bar

        # error: [unresolved-attribute]
        reveal_type(x.spam)  # revealed: Unknown