ruff/crates/red_knot_python_semantic/resources/mdtest/attributes.md
David Peter 907047bf4b
[red-knot] Add tests for member lookup on union types (#14296)
## Summary

- Write tests for member lookups on union types
- Remove TODO comment

part of: #14022

## Test Plan

New MD tests
2024-11-12 14:11:55 +01:00

2.3 KiB

Class attributes

Union of attributes

def bool_instance() -> bool:
    return True

flag = bool_instance()

if flag:
    class C1:
        x = 1

else:
    class C1:
        x = 2

class C2:
    if flag:
        x = 3
    else:
        x = 4

reveal_type(C1.x)  # revealed: Literal[1, 2]
reveal_type(C2.x)  # revealed: Literal[3, 4]

Inherited attributes

class A:
    X = "foo"

class B(A): ...
class C(B): ...

reveal_type(C.X)  # revealed: Literal["foo"]

Inherited attributes (multiple inheritance)

class O: ...

class F(O):
    X = 56

class E(O):
    X = 42

class D(O): ...
class C(D, F): ...
class B(E, D): ...
class A(B, C): ...

# revealed: tuple[Literal[A], Literal[B], Literal[E], Literal[C], Literal[D], Literal[F], Literal[O], Literal[object]]
reveal_type(A.__mro__)

# `E` is earlier in the MRO than `F`, so we should use the type of `E.X`
reveal_type(A.X)  # revealed: Literal[42]

Unions with possibly unbound paths

Definite boundness within a class

In this example, the x attribute is not defined in the C2 element of the union:

def bool_instance() -> bool:
    return True

class C1:
    x = 1

class C2: ...

class C3:
    x = 3

flag1 = bool_instance()
flag2 = bool_instance()

C = C1 if flag1 else C2 if flag2 else C3

# error: [possibly-unbound-attribute] "Attribute `x` on type `Literal[C1, C2, C3]` is possibly unbound"
reveal_type(C.x)  # revealed: Literal[1, 3]

Possibly-unbound within a class

We raise the same diagnostic if the attribute is possibly-unbound in at least one element of the union:

def bool_instance() -> bool:
    return True

class C1:
    x = 1

class C2:
    if bool_instance():
        x = 2

class C3:
    x = 3

flag1 = bool_instance()
flag2 = bool_instance()

C = C1 if flag1 else C2 if flag2 else C3

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

Unions with all paths unbound

If the symbol is unbound in all elements of the union, we detect that:

def bool_instance() -> bool:
    return True

class C1: ...
class C2: ...

flag = bool_instance()

C = C1 if flag else C2

# error: [unresolved-attribute] "Type `Literal[C1, C2]` has no attribute `x`"
reveal_type(C.x)  # revealed: Unknown