# Class subscript

## Class getitem unbound

```py
class NotSubscriptable: ...

# error: "Cannot subscript object of type `<class 'NotSubscriptable'>` with no `__class_getitem__` method"
a = NotSubscriptable[0]
```

## Class getitem

```py
class Identity:
    def __class_getitem__(cls, item: int) -> str:
        return str(item)

reveal_type(Identity[0])  # revealed: str
```

`__class_getitem__` is implicitly a classmethod, so it can be called like this:

```py
reveal_type(Identity.__class_getitem__(0))  # revealed: str
```

## Class getitem union

```py
def _(flag: bool):
    class UnionClassGetItem:
        if flag:
            def __class_getitem__(cls, item: int) -> str:
                return str(item)

        else:
            def __class_getitem__(cls, item: int) -> int:
                return item

    reveal_type(UnionClassGetItem[0])  # revealed: str | int
```

## Class getitem with class union

```py
def _(flag: bool):
    class A:
        def __class_getitem__(cls, item: int) -> str:
            return str(item)

    class B:
        def __class_getitem__(cls, item: int) -> int:
            return item

    x = A if flag else B

    reveal_type(x)  # revealed: <class 'A'> | <class 'B'>
    reveal_type(x[0])  # revealed: str | int
```

## Class getitem with unbound method union

```py
def _(flag: bool):
    if flag:
        class Spam:
            def __class_getitem__(self, x: int) -> str:
                return "foo"

    else:
        class Spam: ...

    # error: [not-subscriptable] "Cannot subscript object of type `<class 'Spam'>` with no `__class_getitem__` method"
    # revealed: str | Unknown
    reveal_type(Spam[42])
```

## Class getitem non-class union

```py
def _(flag: bool):
    if flag:
        class Eggs:
            def __class_getitem__(self, x: int) -> str:
                return "foo"

    else:
        Eggs = 1

    a = Eggs[42]  # error: "Cannot subscript object of type `Literal[1]` with no `__getitem__` method"

    reveal_type(a)  # revealed: str | Unknown
```

## Class getitem on metaclass

`__class_getitem__` is also looked up on the metaclass.

```py
class Meta(type):
    def __class_getitem__(cls, item: int) -> str:
        return str(item)

class WithMeta(metaclass=Meta): ...

reveal_type(WithMeta[0])  # revealed: str
```

## Conflicting class and metaclass getitem

`__class_getitem__` on the class takes precedence over the one on the metaclass.

```py
class Meta(type):
    def __class_getitem__(cls, item: int) -> str:
        return str(item)

class WithMetaAndClassGetItem(metaclass=Meta):
    def __class_getitem__(cls, item: int) -> int:
        return item

reveal_type(WithMetaAndClassGetItem[0])  # revealed: int
```

## Class getitem with getitem on metaclass

`__getitem__` on the metaclass takes precedence over `__class_getitem__` on the class.

```py
class Meta(type):
    def __getitem__(cls, item: int) -> str:
        return str(item)

class WithMetaGetItem(metaclass=Meta):
    def __class_getitem__(cls, item: int) -> int:
        return item

reveal_type(WithMetaGetItem[0])  # revealed: str
```

## Intersection of nominal-instance types

If a subscript operation could succeed for *any* positive element of an intersection, no diagnostic
should be reported even if it would not succeed for some other element of the intersection.

```py
class Foo: ...

class Bar:
    def __getitem__(self, key: str) -> int:
        return 42

def f(x: Foo):
    if isinstance(x, Bar):
        reveal_type(x["whatever"])  # revealed: int
```

When a subscript operation fails due to an invalid argument type, we should report only that error
(not a spurious `not-subscriptable` error for elements that lack `__getitem__`), and the return type
should be inferred from the element that does have `__getitem__`.

```py
class Baz:
    def __getitem__(self, key: int) -> str:
        return ""

class Qux: ...

def g(x: Baz):
    if isinstance(x, Qux):
        # x is Baz & Qux. Baz has __getitem__(int) -> str, Qux has no __getitem__.
        # The intersection IS subscriptable (via Baz), but with wrong argument type.
        # error: [invalid-argument-type]
        reveal_type(x["hello"])  # revealed: str
```

When both elements have `__getitem__` but both fail, we report errors for each element.

```py
class A:
    def __getitem__(self, key: int) -> str:
        return ""

class B:
    def __getitem__(self, key: str) -> int:
        return 0

def h(x: A):
    if isinstance(x, B):
        # x is A & B. A expects int, B expects str. A list satisfies neither.
        # error: [invalid-argument-type]
        # error: [invalid-argument-type]
        reveal_type(x[[]])  # revealed: Never
```

When we have an intersection with only negative elements (e.g., `~int & ~str`), the type is
implicitly `object & ~int & ~str`. Subscripting such a type should use `object` as the positive
element, which is not subscriptable.

```py
def i(x: object):
    if not isinstance(x, (int, str)):
        # x is object & ~int & ~str, which simplifies to ~int & ~str with implicit object
        # error: [not-subscriptable]
        reveal_type(x[0])  # revealed: Unknown
```

When both the value and the slice are intersections, we distribute over both. For `(A & B)[X & Y]`,
we get `(A[X] & A[Y]) & (B[X] & B[Y])`.

```py
from ty_extensions import Intersection

class ResultA: ...
class ResultB: ...

class ContainerA:
    def __getitem__(self, key: int) -> ResultA:
        return ResultA()

class ContainerB:
    def __getitem__(self, key: int) -> ResultB:
        return ResultB()

class IndexA(int): ...
class IndexB(int): ...

def j(
    container_a: ContainerA,
    container_b: ContainerB,
    container_ab: Intersection[ContainerA, ContainerB],
    index_a: IndexA,
    index_ab: Intersection[IndexA, IndexB],
):
    # Single container, single index
    reveal_type(container_a[index_a])  # revealed: ResultA

    # Single container, intersection index distributes over the index:
    # ContainerA[IndexA] & ContainerA[IndexB] = ResultA & ResultA = ResultA
    reveal_type(container_a[index_ab])  # revealed: ResultA

    # Intersection container, single index distributes over the container:
    # ContainerA[IndexA] & ContainerB[IndexA] = ResultA & ResultB
    reveal_type(container_ab[index_a])  # revealed: ResultA & ResultB

    # Both are intersections: distributes over both
    # (ContainerA[IndexA] & ContainerA[IndexB]) & (ContainerB[IndexA] & ContainerB[IndexB])
    # = (ResultA & ResultA) & (ResultB & ResultB) = ResultA & ResultB
    reveal_type(container_ab[index_ab])  # revealed: ResultA & ResultB
```
