mirror of https://github.com/astral-sh/ruff
Merge ebe8cdd2b1 into 5b1d3ac9b9
This commit is contained in:
commit
6b8ecc6eb2
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Red-knot internals
|
||||||
|
|
||||||
|
This directory contains internal documentation for developers of the red-knot type checker.
|
||||||
|
|
@ -0,0 +1,263 @@
|
||||||
|
# Kinds of types
|
||||||
|
|
||||||
|
This document provides definitions for various kinds of types and classes,
|
||||||
|
with examples of Python types and classes that satisfy these definitions.
|
||||||
|
|
||||||
|
It doesn't attempt to be exhaustive.
|
||||||
|
Definitions should only be added to this document if they are:
|
||||||
|
|
||||||
|
1. Not covered by the ["Type system concepts" section of the Python typing spec](https://typing.readthedocs.io/en/latest/spec/concepts.html)
|
||||||
|
1. Definitions that would be useful for developers working on red-knot.
|
||||||
|
|
||||||
|
## Types
|
||||||
|
|
||||||
|
A static Python type represents a set of possible values at runtime.
|
||||||
|
The number of ways in which possible Python objects could be categorised into sets and subsets is infinite;
|
||||||
|
thus, there is an infinite number of possible types in Python.
|
||||||
|
|
||||||
|
The [typing spec](https://typing.readthedocs.io/en/latest/spec/) specifies various kinds of types
|
||||||
|
that all Python type checkers should support,
|
||||||
|
The desirable type checker behavior has been standardised to some degree for these specified types,
|
||||||
|
but the spec is by no means exhaustive.
|
||||||
|
There are more kinds of types in Python than are included in the spec,
|
||||||
|
and many kinds of types that are left unspecified must be understood by a type checker
|
||||||
|
in order for the type checker to accurately and precisely model Python's runtime semantics.
|
||||||
|
|
||||||
|
Some types in Python have a known size.
|
||||||
|
Most, however, have an infinite number of possible inhabitants and subtypes.
|
||||||
|
For any type `T`, the union of all of the proper subtypes[^2] of `T` is exactly equal to `T`.
|
||||||
|
|
||||||
|
## Singleton types
|
||||||
|
|
||||||
|
A singleton type is a type for which it is known that there is
|
||||||
|
(and can only ever be) exactly one inhabitant of the type at runtime:
|
||||||
|
a set of runtime values with size exactly 1.
|
||||||
|
For any singleton type in Python, the type's sole inhabitant will always exist
|
||||||
|
at the same memory address for the entire duration of a Python program.
|
||||||
|
|
||||||
|
Examples of singleton types in red-knot's model of Python include:
|
||||||
|
|
||||||
|
- `types.EllipsisType` (the sole inhabitant is `builtins.Ellipsis`;
|
||||||
|
the constructor always returns the same object):
|
||||||
|
|
||||||
|
```pycon
|
||||||
|
>>> from types import EllipsisType
|
||||||
|
>>> EllipsisType() is EllipsisType() is ... is Ellipsis
|
||||||
|
True
|
||||||
|
>>> id(EllipsisType()) == id(...)
|
||||||
|
True
|
||||||
|
```
|
||||||
|
|
||||||
|
- `types.NotImplementedType`
|
||||||
|
(in the same way as `types.EllipsisType` and `builtins.Ellipsis`,
|
||||||
|
the sole inhabitant is `builtins.NotImplemented`).
|
||||||
|
|
||||||
|
- `None` (which can also be spelled as `Literal[None]`).
|
||||||
|
The sole inhabitant of the type is the runtime constant `None` itself.
|
||||||
|
|
||||||
|
- `Literal[True]`: the sole inhabitant of the type is the constant `True`.
|
||||||
|
|
||||||
|
- `Literal[False]`: the sole inhabitant of the type is the constant `False`.
|
||||||
|
|
||||||
|
- `Literal[E.A]`, `Literal[E.B]` or `Literal[E.C]` for the following enum `E`
|
||||||
|
(the sole inhabitant of `Literal[E.A]` is the enum member `E.A`):
|
||||||
|
|
||||||
|
```py
|
||||||
|
from enum import Enum, auto
|
||||||
|
|
||||||
|
class E(Enum):
|
||||||
|
A = auto()
|
||||||
|
B = auto()
|
||||||
|
C = auto()
|
||||||
|
```
|
||||||
|
|
||||||
|
- A "literal class type": a type representing a single known class (and none of its possible subclasses).
|
||||||
|
|
||||||
|
- A "literal function type": a type that represents a single known function
|
||||||
|
(and excludes all other functions, even if they have the same signature).
|
||||||
|
|
||||||
|
- A "literal module type": a type representing a single known module.
|
||||||
|
|
||||||
|
Since it is known that all inhabitants of a given singleton type share the same memory address,
|
||||||
|
singleton types in unions can be safely narrowed by identity,
|
||||||
|
using [the operators `is` and `is not`](https://snarky.ca/unravelling-is-and-is-not/):
|
||||||
|
|
||||||
|
```py
|
||||||
|
def f(x: str | None):
|
||||||
|
if x is None:
|
||||||
|
... # x can be narrowed to `str`
|
||||||
|
else:
|
||||||
|
... # x can be narrowed to `None`
|
||||||
|
```
|
||||||
|
|
||||||
|
All Python singleton types are also sealed types and final types; nearly all are single-value types.
|
||||||
|
(See below for definitions of these other concepts.)
|
||||||
|
|
||||||
|
The number of singleton types in Python is theoretically infinite
|
||||||
|
(for any given theoretical runtime object `x`, you could theorise a singleton type `Y` for which
|
||||||
|
the sole inhabitant of `Y` would be the runtime object `x`). However, only a small fraction of
|
||||||
|
the set of possible singleton types are understood by red-knot's model.
|
||||||
|
|
||||||
|
## Sealed types
|
||||||
|
|
||||||
|
A sealed type is a type for which it is known
|
||||||
|
that there is only a finite and pre-determined set of inhabitants at runtime.
|
||||||
|
It follows from this that for every sealed type,
|
||||||
|
there is also only a finite and pre-determined set of *subtypes* of that type.
|
||||||
|
|
||||||
|
All singleton types are sealed types; however, not all sealed types are singleton types.
|
||||||
|
|
||||||
|
Examples of sealed types (other than the singleton types listed above) are:
|
||||||
|
|
||||||
|
- `bool`:
|
||||||
|
|
||||||
|
- The only inhabitants of `bool` at runtime are the constants `True` and `False`.
|
||||||
|
- The only proper subtypes of `bool` are `Literal[True]`, `Literal[False]`, and `Never`.
|
||||||
|
|
||||||
|
- Enums: consider the following enum class:
|
||||||
|
|
||||||
|
```py
|
||||||
|
from enum import Enum, auto
|
||||||
|
|
||||||
|
class Foo(Enum):
|
||||||
|
X = auto()
|
||||||
|
Y = auto()
|
||||||
|
Z = auto()
|
||||||
|
```
|
||||||
|
|
||||||
|
- The only inhabitants of the `Foo` type at runtime are the enum `Foo`'s members:
|
||||||
|
`Foo.X`, `Foo.Y` and `Foo.Z`.
|
||||||
|
|
||||||
|
- The only proper subtypes of `Foo` are `Literal[Foo.X]`, `Literal[Foo.Y]`, `Literal[Foo.Z]`,
|
||||||
|
`Literal[Foo.X, Foo.Y]`, `Literal[Foo.X, Foo.Z]`, `Literal[Foo.Y, Foo.Z]`, and `Never`.
|
||||||
|
|
||||||
|
For a given sealed type `X` where the only proper subtypes of `X` are `A`, `B` and `C`,
|
||||||
|
`X & ~A == B | C`.
|
||||||
|
|
||||||
|
Due to the enumerability of a sealed type's inhabitants,
|
||||||
|
a Python sealed type can be narrowed using identity:
|
||||||
|
|
||||||
|
```py
|
||||||
|
from enum import Enum, auto
|
||||||
|
|
||||||
|
class Colors(Enum):
|
||||||
|
RED = auto()
|
||||||
|
BLUE = auto()
|
||||||
|
YELLOW = auto()
|
||||||
|
|
||||||
|
def f(var: X):
|
||||||
|
... # the type of `var` here is `X` (which is equivalent to `Literal[Colors.RED, Colors.BLUE, Colors.YELLOW]`)
|
||||||
|
|
||||||
|
if var is Colors.RED:
|
||||||
|
... # var is narrowed to `Literal[Colors.RED]`
|
||||||
|
else:
|
||||||
|
... # var is narrowed to `Literal[Colors.BLUE, Colors.YELLOW]`
|
||||||
|
```
|
||||||
|
|
||||||
|
## Single-value types
|
||||||
|
|
||||||
|
For a given type `T`, `T` can be said to be a single-value type if there exists an
|
||||||
|
[equivalence relation](https://en.wikipedia.org/wiki/Equivalence_relation)
|
||||||
|
between all inhabitants of the type and the equivalence relation is satisfied between
|
||||||
|
all inhabitants of the type.
|
||||||
|
|
||||||
|
For a Python type `T` to be categorised as a single-value type:
|
||||||
|
|
||||||
|
- All inhabitants of `T` must be instances of the same runtime class `U`
|
||||||
|
- `U` must have `__eq__` and `__ne__` methods such that the equality relation between instances of `U`
|
||||||
|
is reflexive, symmetric, and transitive (satisfying the definition of
|
||||||
|
an equivalence relation.)
|
||||||
|
- For any two inhabitants of `T` named `x` and `y`, `x == y`.
|
||||||
|
|
||||||
|
Nearly all singleton types are single-value types, but the reverse is not true.
|
||||||
|
Many single-value types exist that are not singleton types: examples include
|
||||||
|
`Literal[123456]`, `Literal[b"foo"]`, `tuple[Literal[True], Literal[False]]`, and `Literal["foo"]`.
|
||||||
|
All runtime inhabitants of the `Literal["foo"]` type are equal to the string `"foo"`.
|
||||||
|
However, they are not necessarily the *same* object;
|
||||||
|
multiple `str` instances equal to `"foo"` can exist at runtime at different memory addresses.[^1]
|
||||||
|
This means that it is not safe to narrow a single-value type by identity unless it is also known
|
||||||
|
that the type is a sealed type.
|
||||||
|
|
||||||
|
Single-value types in unions can be safely narrowed using inequality (but not using equality):
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
def f(x: str | Literal[1]):
|
||||||
|
if x != 1:
|
||||||
|
... # `x` can be narrowed to `str` if not equal to `1`
|
||||||
|
else:
|
||||||
|
... # type of `x` is still `str | Literal[1]`
|
||||||
|
# (`x` could be an instance of a `str` subclass that overrides `__eq__`
|
||||||
|
# to compare equal with `1`)
|
||||||
|
|
||||||
|
if x == 1:
|
||||||
|
... # type of `x` is still `str | Literal[1]`
|
||||||
|
# (`x` could be an instance of a `str` subclass that overrides `__eq__`
|
||||||
|
# to compare equal with `1`)
|
||||||
|
else:
|
||||||
|
... # `x` can be narrowed to `str` if not equal to `1`
|
||||||
|
```
|
||||||
|
|
||||||
|
An example of a singleton type that is not a single-value type
|
||||||
|
would be `Literal[Ham.A]` for the following `Ham` enum:
|
||||||
|
because the `Ham` class overrides `__eq__`, we can no longer say for sure
|
||||||
|
that the `Ham` type can be safely narrowed using inequality as in the above example
|
||||||
|
(but we *can* still narrow the `Ham` type using identity, as with all singleton types):
|
||||||
|
|
||||||
|
```py
|
||||||
|
from enum import Enum, auto
|
||||||
|
|
||||||
|
class Ham(Enum):
|
||||||
|
A = auto()
|
||||||
|
B = auto()
|
||||||
|
C = auto()
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return False
|
||||||
|
```
|
||||||
|
|
||||||
|
## Final classes
|
||||||
|
|
||||||
|
A "final class" in Python is a class that can be statically inferred as being unsubclassable.
|
||||||
|
Most final classes are decorated with [`@typing.final`](https://docs.python.org/3/library/typing.html#typing.final),
|
||||||
|
a special decorator that indicates to the type checker that the tool should assume
|
||||||
|
the class cannot be subclassed, and that the tool should emit an error
|
||||||
|
if it detects an attempt to do so. Not all final classes are decorated with `@final`, however.
|
||||||
|
The most common example of this is enum classes. Any enum class that has at least one member is considered
|
||||||
|
implicitly final, as attempting to subclass such a class will fail at runtime.
|
||||||
|
|
||||||
|
For any two classes `X` and `Y`, if `X` is final and `X` is not a subclass of `Y`,
|
||||||
|
the intersection of the types `X & Y` is equivalent to `Never`, the empty set.
|
||||||
|
This therefore means that for a type defined as being "all instances of the final class `X`",
|
||||||
|
there are fewer ways of subtyping such a type than for most types in Python.
|
||||||
|
However, final class types can still have non-`Never` proper subtypes.
|
||||||
|
Enums again provide a good example here: `Literal[Animals.LION]` is a proper subtype
|
||||||
|
of the `Animals` type here, despite the fact that the `Animals` class cannot be subclassed:
|
||||||
|
|
||||||
|
```py
|
||||||
|
from enum import Enum, auto
|
||||||
|
|
||||||
|
class Animals(Enum):
|
||||||
|
LION = auto()
|
||||||
|
LEOPARD = auto()
|
||||||
|
CHEETAH = auto()
|
||||||
|
```
|
||||||
|
|
||||||
|
Many singleton and sealed types are associated with final classes: the classes
|
||||||
|
`types.EllipsisType`, `types.NotImplementedType`, `types.NoneType`, `bool`,
|
||||||
|
and all enum classes are final classes. However, there are also many singleton
|
||||||
|
and sealed types (some representable, but some merely theoretical)
|
||||||
|
that cannot be defined as "all instances of a specific final class `X`".
|
||||||
|
In the above enum, `Literal[Animals.LION]` is a singleton type and a sealed type,
|
||||||
|
but would not fit this definition.
|
||||||
|
|
||||||
|
[^2]: For a given type `X`, a "proper subtype" of `X` is defined
|
||||||
|
as a type that is strictly narrower than `X`. The set of proper subtypes of `X` includes
|
||||||
|
all of `X`'s subtypes *except* for `X` itself.
|
||||||
|
|
||||||
|
[^1]: Whether or not different inhabitants of a given single-value type *actually* exist
|
||||||
|
at different memory addresses is an implementation detail at runtime
|
||||||
|
which cannot be determined or relied upon by a static type checker.
|
||||||
|
It may vary according to the specific value in question,
|
||||||
|
and/or whether the user is running CPython, PyPy, GraalPy, or IronPython.
|
||||||
Loading…
Reference in New Issue