overlapping¶
Overlapping[T] is a "safe variance" escape hatch for input positions.
it lets a covariant (out T) class declare a
method that consumes T without giving up covariance:
def f(xs: list[int], o: object):
1 in xs # ok - 1 overlaps with int
o in xs # ok - object overlaps with int
"a" in xs # error - str does not overlap with int
it is the loose sibling of SafeVariance: both are asymmetric
(real type at the call site, bound inside the body); they differ only in the
call-site relation
the two sides¶
class Mapping[out Key, out Value]:
def __contains__(self, key: Overlapping[Key]) -> bool:
reveal_type(key) # object — the upper bound of Key
return True
def f(m: Mapping[int, object], o: object):
1 in m # ok — int overlaps int
o in m # ok — object overlaps int (it could be an int)
"a" in m # error — str is disjoint from int
- at the call site, an argument is accepted iff it is not disjoint from the
specialized
Key— i.e. their types overlap (Overlapping[T]means exactly "not disjoint fromT"). so a provably-unrelated argument like"a"is rejected, but a could-be-a-Keyargument likeois allowed. this is looser than a plainKeyparameter (which would rejecto) and stricter thanobject(which would accept"a") - inside the body, the parameter is seen as the upper bound of
Key, so the consumed value can never be written back intoKey-typed covariant storage. that erasure is the soundness guard: the value flows in at full precision and is immediately erased to the bound, so it can never flow back out mislabelled
the membership use case¶
the canonical use is the __contains__ method of a covariant container, as in
the example above. a membership test only makes sense for a value that could be
an element, so basedpython's typeshed types Container.__contains__ as
Overlapping[Element]
Container.__contains__ is the abstract requirement, and every container's
__contains__ takes Overlapping[Element] for its own element, so the check
applies uniformly to list, set, dict keys, tuple, and any user class
deriving from Container/Collection. dict.__getitem__ and dict.get (and
Mapping.__getitem__/get) consume the covariant key the same way, so a lookup
d[k] accepts exactly the keys a membership test k in d would
because Overlapping[Key] relates as Key for subtyping and override
compatibility (only the call site applies the overlap admissibility check), a
subclass can override such a method with the bare Key (or its upper bound)
without a Liskov violation
a membership test whose operands can never overlap is a definite bug, so in and
not in report unsupported-operator when the value is provably disjoint from
the container's element — including narrowing residuals and empty containers.
this is stricter than mainstream checkers (mypy gates the same check behind
--strict-equality); in basedpython it is always on
overlap is exact¶
Overlapping uses the type system's disjointness relation, so it is precise
about literals and unions:
def f(b: list[bool], keys: dict[int | str, object]):
True in b # ok — bool overlaps bool
1 in b # error — Literal[1] is not a bool
b"x" in keys # error — bytes is disjoint from int | str
a union is accepted whenever any member overlaps, matching the whole-operand
behaviour of a membership test — x: str | None may be tested against a
dict[str, int] because its str part overlaps the key
precision cuts the other way for a bare constructor call: object() is inferred
final object, meaning
exactly object and never a subclass, which is disjoint from int. so
object() in xs is rejected where a value declared o: object is accepted
Overlapping[T] is a parameter annotation¶
Overlapping[T] is only meaningful as a parameter annotation. inside the body
and everywhere else it behaves as the upper bound of T. in basedpython source
and in the vendored typeshed it resolves without an import; user code in plain
Python imports it from ty_extensions