# Attributes Tests for attribute access on various kinds of types. ## Union of attributes ```py def _(flag: bool): 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 ```py class A: X = "foo" class B(A): ... class C(B): ... reveal_type(C.X) # revealed: Literal["foo"] ``` ## Inherited attributes (multiple inheritance) ```py 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: ```py def _(flag1: bool, flag2: bool): class C1: x = 1 class C2: ... class C3: x = 3 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: ```py def _(flag: bool, flag1: bool, flag2: bool): class C1: x = 1 class C2: if flag: x = 2 class C3: x = 3 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: ```py def _(flag: bool): class C1: ... class C2: ... C = C1 if flag else C2 # error: [unresolved-attribute] "Type `Literal[C1, C2]` has no attribute `x`" reveal_type(C.x) # revealed: Unknown ``` ## Objects of all types have a `__class__` method ```py import typing_extensions reveal_type(typing_extensions.__class__) # revealed: Literal[ModuleType] a = 42 reveal_type(a.__class__) # revealed: Literal[int] b = "42" reveal_type(b.__class__) # revealed: Literal[str] c = b"42" reveal_type(c.__class__) # revealed: Literal[bytes] d = True reveal_type(d.__class__) # revealed: Literal[bool] e = (42, 42) reveal_type(e.__class__) # revealed: Literal[tuple] def f(a: int, b: typing_extensions.LiteralString, c: int | str, d: type[str]): reveal_type(a.__class__) # revealed: type[int] reveal_type(b.__class__) # revealed: Literal[str] reveal_type(c.__class__) # revealed: type[int] | type[str] # `type[type]`, a.k.a., either the class `type` or some subclass of `type`. # It would be incorrect to infer `Literal[type]` here, # as `c` could be some subclass of `str` with a custom metaclass. # All we know is that the metaclass must be a (non-strict) subclass of `type`. reveal_type(d.__class__) # revealed: type[type] reveal_type(f.__class__) # revealed: Literal[FunctionType] class Foo: ... reveal_type(Foo.__class__) # revealed: Literal[type] ``` ## Function-literal attributes Most attribute accesses on function-literal types are delegated to `types.FunctionType`, since all functions are instances of that class: ```py path=a.py def f(): ... reveal_type(f.__defaults__) # revealed: @Todo(instance attributes) reveal_type(f.__kwdefaults__) # revealed: @Todo(instance attributes) ``` Some attributes are special-cased, however: ```py path=b.py def f(): ... reveal_type(f.__get__) # revealed: @Todo(`__get__` method on functions) reveal_type(f.__call__) # revealed: @Todo(`__call__` method on functions) ``` ## Int-literal attributes Most attribute accesses on int-literal types are delegated to `builtins.int`, since all literal integers are instances of that class: ```py path=a.py reveal_type((2).bit_length) # revealed: @Todo(instance attributes) reveal_type((2).denominator) # revealed: @Todo(instance attributes) ``` Some attributes are special-cased, however: ```py path=b.py reveal_type((2).numerator) # revealed: Literal[2] reveal_type((2).real) # revealed: Literal[2] ``` ## Literal `bool` attributes Most attribute accesses on bool-literal types are delegated to `builtins.bool`, since all literal bols are instances of that class: ```py path=a.py reveal_type(True.__and__) # revealed: @Todo(instance attributes) reveal_type(False.__or__) # revealed: @Todo(instance attributes) ``` Some attributes are special-cased, however: ```py path=b.py reveal_type(True.numerator) # revealed: Literal[1] reveal_type(False.real) # revealed: Literal[0] ``` ## Bytes-literal attributes All attribute access on literal `bytes` types is currently delegated to `buitins.bytes`: ```py reveal_type(b"foo".join) # revealed: @Todo(instance attributes) reveal_type(b"foo".endswith) # revealed: @Todo(instance attributes) ```