Repository navigation
Overlapping type #2154
Description
Activity
- addedtopic: featureDiscussions about new features for Python's type annotationsDiscussions about new features for Python's type annotations
on Jan 19, 2026 I've also thought about this but I think it can't be made sound. Consider:
y: int = 1 x: Overlapping[str] = y # error y: int = 1 tmp: object = y # ok x: Overlapping[str] = tmp # ok@randolf-scholz's suggestion that intersection types would help here seems incorrect. In the example, we have a method
popwith parameter typeT & str. Whatever T is,T & stris a subtype ofstr, andint | stris not a subtype ofstr, sopop(x)wherex: int | strshould not be allowed.Reacted by Randolf ScholzSuch an overlapping type would equal
objectin the implementation, and would only be checked in input locations. This is actually consistent with the problem it's supposed to solve.I agree with the concern about soundness, but I think the practical problem is still real and currently unsolved in a usable way.
What people want to express with Overlapping[KT] is not a real runtime type, but an argument constraint: “accept anything that might be a KT at runtime”. That is inherently input-only and contravariant, which already puts it outside what Python’s nominal type system can model soundly.
From that perspective, treating it as object at runtime (as srittau suggests) but checking overlap only at call sites is actually consistent with how we already accept other unsound-but-useful typing features (e.g. Any, bivariance of mutable containers, most overloads in typeshed).
The int | str case is exactly the pain point:
rejecting it is technically sound but annoying in practice
accepting it is unsound but matches runtime behavior and user expectations for APIs like dict.pop, get, contains, etc.
I don’t think intersection types alone solve this, but a checker-only “overlap constraint” (not a real type) might still be worth standardizing, even if it’s explicitly documented as unsound. Otherwise, we’re stuck with object + overload hacks forever.
Reacted by Sebastian Rittau, Jelle Zijlstra, Randolf Scholz and Ashrith SagarI just added a prototype of this feature to pycroscope. It was quite simple to implement: JelleZijlstra/pycroscope#291.
Still a bit concerned about how this fits in with the set-theoretic formalization of the type system, but it seems like a useful and simple feature.
$ pycroscope tmp/overlap.py Incompatible argument type for x: expected Overlapping[int] but got str (code: incompatible_argument) str is not assignable to Overlapping[int] In tmp/overlap.py at line 7 4: pass 5: 6: def caller(s: str, i_s: int | str, i: int): 7: f(s) ^ 8: f(i_s) 9: f(i) 10:
This is an rough idea from the recent discussion of whether to use
object/Anyor_KTfor arguments of certain methods likepopor__sub__. E.g.:What should
Xbe? UsingKT(orKT | None) finds type errors like the following:But has a problem with overlapping types:
On the other hand, using
objectdoesn't catch type errors likepoppable.pop(123).The solution would be able to specify that any partially overlapping type would be accepted:
MyPoppable[str]().pop()would acceptstr,str | None,Literal["x"],str | int, etc., but not e.g. plainint.