type reification¶
standard python erases inferred specializations: A(1) constructs an A
with no record that it was an A[int]. the transpiler makes the inferred
specialization of a user-defined generic constructor explicit in the generated
python:
→
A[int](…) routes through types.GenericAlias.__call__, which stamps
__orig_class__ on the constructed instance — the specialization becomes an
observable runtime value:
the stamp is what makes a runtime specialization visible — see
parametric type tests for x is A[int], which
reads it back.
collection literals stay bare¶
a display is left exactly as written, even where its element type is known:
the builtin collections silently reject the __orig_class__ stamp, so
list[int]([1, 2]) constructs a value indistinguishable from [1, 2] — the
wrap would carry no runtime information and cost a constructor call
that is why a parametric test cannot read a specialization back off a builtin:
x is list[int] resolves statically or through a reified
type parameter instead
where the types come from¶
the injected spelling is read from the specialization ty already inferred for
the expression — the same solution the checker reports — promoting each
argument as it is spelled (Literal[1] → int, since only a class object can
be written at runtime; a covariant parameter keeps its literal in the checker's
own view, see fluid specializations). there is no
separate solver, so the injected arguments never disagree with the checker, and
usage-based widening of an inferred specialization flows straight into the
injection
an explicit specialization is always kept as written: A[int](1) transpiles
unchanged, and is never wrapped twice
best-effort, never an error¶
unlike reified type parameters — where the runtime needs the type argument and an uninjectable bare call is a checker error — constructor reification changes nothing the body can observe, so it simply doesn't fire when no runtime spelling exists:
- an unsolved or dynamic argument —
A()inferred asA[Unknown], orA(x)for an unannotatedx - a type argument with no spelling at the call site, such as a class defined
inside a function:
A(Local())stays bare - a non-generic class, which has no specialization to make explicit
what never reifies¶
- type expressions: annotations, type-parameter lists,
type X = …values, and type-context subscript slices (the[int]list of a legacyCallable[[int], str]is type syntax, not a value) - the values of dunders that static readers consume structurally:
__all__,__slots__,__match_args__ sys.version_infocomparisons — every static reader (including ty on the generated python) must see the literal tuple gate- function parameter defaults — a non-scalar default is consumed whole by the mutable defaults lowering and re-evaluated in a body guard. lambda defaults are not sentinel-lowered, so they do reify
- stubs (no runtime to observe) and targets below python 3.9