typeshed improvements¶
basedpython vendors typeshed as .byi stubs, regenerated from upstream on every
sync and then patched deterministically. some of those patches change what the
stdlib's types mean; the rest change how they read. both are listed here —
typeshed patches covers how the machinery
works
type fixes¶
what basedpython's stdlib says that upstream's does not
mapping keys are covariant¶
upstream declares Mapping with an invariant key. basedpython makes it
covariant, so a Mapping[str, int] is a Mapping[object, int]:
MutableMapping keeps an invariant key — __setitem__ needs it
re capture groups are optional, not Any¶
upstream types every "this group may not have participated" position as
AnyStr | MaybeNone, and MaybeNone is Any — so calling a str method on a
group that is None at runtime passes silently. basedpython spells the
possibility out:
this covers Match.group, Match.groups, Match.groupdict,
Match.__getitem__ and the split functions. where the pattern is a literal,
regex group types reads it and gives something exact instead;
this is the fallback for a pattern the checker cannot see
membership tests check for overlap¶
Container is covariant in its element, so in consumes a covariant type
variable in an input position. upstream gives up and types the parameter
object. basedpython types it Overlapping[Element] — a
value is accepted if it is not disjoint from the element type:
def f(xs: list[int], o: object):
1 in xs # ok
o in xs # ok — `object` overlaps `int`
"a" in xs # error — `str` and `int` are disjoint
Mapping and dict apply the same treatment to __getitem__ and get, which
consume the covariant key
a fresh container widens at the call site¶
an invariant container cannot be assigned to a wider specialization — a caller
holding list[int | None] could insert a None into your list[int]. but a
method returning a brand new container (list.copy, list.__add__, the set
algebra, dict.copy, ...) hands back an object the caller solely owns, so
widening it is sound:
a: list[int] = [1]
b: list[int | None] = a.copy() # ok — nothing else holds the copy
reveal_type(a.copy()) # still `list[int]`
it is a Never-defaulted type parameter unioned into the return type, so with
no expected type inference is unchanged
functools.cache keeps the wrapped signature¶
upstream parametrizes _lru_cache_wrapper by the return type only, so a cached
function loses its parameter list:
basedpython captures the whole callable and recovers the signature through
generic self-binding, with a __get__ overload so a cached method is checked
too — no ParamSpec or Concatenate spelling needed
hashable keys are required¶
the key of dict and frozendict, and the element of set and frozenset,
are bounded by Hashable — an unhashable key is a type error rather than a
runtime TypeError
more covariance in builtins¶
frozendictis fully covariant — it has no mutators, so there is nothing to make it invariant- the value projection of
type.__dict__is covariant
borrowing builtins are marked local¶
the builtins that cannot retain their argument take it as
local, so the checker knows the value does not escape
the call
context-manager entry methods are abstract¶
AbstractContextManager.__enter__ and AbstractAsyncContextManager.__aenter__
return self, which the type system cannot spell, so upstream marks them
abstract
deletions¶
- mypy/pyright-only overloads — typeshed carries overloads whose sole
purpose is to nudge another checker's inference, and which their own comments
describe as technically covered by a more general overload.
builtins.getattris the clearest case. ty does not need them - dead symbols —
builtins.function, a@type_check_onlystand-in ty models natively asFunctionType, andtyping.AwaitableGenerator, which upstream itself marks obsolete
idiom rewrites¶
same meaning, written the way a .by file writes it
- pep 695 headers — every legacy
TypeVar(...)+Generic[...]class becomes a pep 695 header with explicit variance (out/in/in out) and readable type-parameter names (_KT_co→Key,_T_co→Element). this is the bulk of the diff protocolkeyword —class C(Base, Protocol)→protocol C(Base)- arrow callables —
Callable[[A, B], R]→(A, B) -> R - bare literals —
Literal[a, b]→a | b finalmodifier — a@finaldecorator stacked with others becomes thefinalclass or def modifierfinaldeclarations —x: Final[T]→final x: Tinitshorthand — a plaindef __init__(self, ...) -> None→init(self, ...)- redundant
-> None— a return annotation that only repeats what a baredefalready means is dropped, sinceNoneis what a stub with no annotation returns. one that would change the type if deleted — an override, a generator — is kept - read-only properties — a non-computed
@property→ a valuelesslet NAME: T, which declares the same thing without the descriptor machinery - type-alias statements — a non-generic
X: TypeAlias = V→type X = V - private aliases and protocols — an underscore-prefixed alias or protocol
that nothing outside its module uses →
private type X/private protocol X - homogeneous tuples —
tuple[X, ...]→(*: X) dynamic— every survivingAny→dynamic- implicit typing imports — the
from typing import ...names basedpython provides implicitly are dropped; runtime helpers stay - cleanups — another checker's suppression comments, leftover
: ...bodies on decorated stubs, stranded private typevars, and stray upstream comments about mypy quirks are all removed