ruff/crates/ty_python_semantic/resources/mdtest/scopes/unbound.md
Alex Waygood 1f8297cfe6
[ty] Improve error messages for unresolved attribute diagnostics (#20963)
## Summary

- Type checkers (and type-checker authors) think in terms of types, but
I think most Python users think in terms of values. Rather than saying
that a _type_ `X` "has no attribute `foo`" (which I think sounds strange
to many users), say that "an object of type `X` has no attribute `foo`"
- Special-case certain types so that the diagnostic messages read more
like normal English: rather than saying "Type `<class 'Foo'>` has no
attribute `bar`" or "Object of type `<class 'Foo'>` has no attribute
`bar`", just say "Class `Foo` has no attribute `bar`"

## Test Plan

Mdtests and snapshots updated
2025-10-19 10:58:25 +01:00

1.3 KiB

Unbound

Unbound class variable

Name lookups within a class scope fall back to globals, but lookups of class attributes don't.

def coinflip() -> bool:
    return True

flag = coinflip()
x = 1

class C:
    y = x
    if flag:
        x = 2

# error: [possibly-missing-attribute] "Attribute `x` may be missing on class `C`"
reveal_type(C.x)  # revealed: Unknown | Literal[2]
reveal_type(C.y)  # revealed: Unknown | Literal[1]

Possibly unbound in class and global scope

def coinflip() -> bool:
    return True

if coinflip():
    x = "abc"

class C:
    if coinflip():
        x = 1

    # Possibly unbound variables in enclosing scopes are considered bound.
    y = x

reveal_type(C.y)  # revealed: Unknown | Literal[1, "abc"]

Possibly unbound in class scope with multiple declarations

def coinflip() -> bool:
    return True

class C:
    if coinflip():
        x: int = 1
    elif coinflip():
        x: str = "abc"

# error: [possibly-missing-attribute]
reveal_type(C.x)  # revealed: int | str

Unbound function local

An unbound function local that has definitions in the scope does not fall back to globals.

x = 1

def f():
    # error: [unresolved-reference]
    # revealed: Unknown
    reveal_type(x)
    x = 2
    # revealed: Literal[2]
    reveal_type(x)