Files
ruff/crates/red_knot_python_semantic/resources/mdtest/assignment/unbound.md
David Peter f1f3bd1cd3 [red-knot] Review remaining 'possibly unbound' call sites (#14284)
## Summary

- Emit diagnostics when looking up (possibly) unbound attributes
- More explicit test assertions for unbound symbols
- Review remaining call sites of `Symbol::ignore_possibly_unbound`. Most
of them are something like `builtins_symbol(self.db,
"Ellipsis").ignore_possibly_unbound().unwrap_or(Type::Unknown)` which
look okay to me, unless we want to emit additional diagnostics. There is
one additional case in enum literal handling, which has a TODO comment
anyway.

part of #14022

## Test Plan

New MD tests for (possibly) unbound attributes.
2024-11-11 20:48:49 +01:00

1.5 KiB

Unbound

Unbound

x = foo  # error: [unresolved-reference] "Name `foo` used when not defined"
foo = 1

# No error `unresolved-reference` diagnostic is reported for `x`. This is
# desirable because we would get a lot of cascading errors even though there
# is only one root cause (the unbound variable `foo`).

# revealed: Unknown
reveal_type(x)

Note: in this particular example, one could argue that the most likely error would be a wrong order of the x/foo definitions, and so it could be desirable to infer Literal[1] for the type of x. On the other hand, there might be a variable fob a little higher up in this file, and the actual error might have been just a typo. Inferring Unknown thus seems like the safest option.

Unbound class variable

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

def bool_instance() -> bool:
    return True

flag = bool_instance()
x = 1

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

# error: [possibly-unbound-attribute] "Attribute `x` on type `Literal[C]` is possibly unbound"
reveal_type(C.x)  # revealed: Literal[2]
reveal_type(C.y)  # revealed: Literal[1]

Possibly unbound in class and global scope

def bool_instance() -> bool:
    return True

if bool_instance():
    x = "abc"

class C:
    if bool_instance():
        x = 1

    # error: [possibly-unresolved-reference]
    y = x

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