The subset¶
This page covers what the compiler accepts, how far PPy stays compatible with Python, and how it treats a value whose type is missing.
What the compiler accepts¶
The compiler analyzes the whole project as one call graph. Inside it:
- Every parameter and return type must be declared or inferable. An implicit
Anyis an error (E1201). It is not silently accepted. - Attributes are resolved on statically known types.
__init__declares the instance fields. eval,exec,from x import *, computed imports, monkey-patching, frame manipulation, computed base classes, and unvouched metaclasses are rejected (E15xx) unless isolated behindppy.dynamic.-
Class construction must be declarative. These are all
E1507:- a class body that executes statements
- a body value constructing a project descriptor whose
__set_name__runs at creation - a base whose
__init_subclass__does real work
The strict checker and the safe hoister judge this from the same shared facts (
class_constructioninanalysis/decorators.py), so they cannot disagree about what a class body runs. - A decorator must have vouched semantics: the built-in table, a plugin, orppy.*. A decorator may replace the decorated object, so believing thedefwhile the runtime holds whatever the decorator returned would be unsound. An unvouched decorator isE1204unless the definition is marked@ppy.dynamic.@partial(vouched, ...)counts as the vouched decorator it binds. - Everything else is ordinary Python: classes, generators, closures,match, comprehensions, decorators the compiler knows, and the stdlib it models.
Strict mode is the default. --no-strict downgrades only the errors that have
a sound fallback.
Compatibility policy¶
PPy makes three separate compatibility claims and holds each to a different standard:
| claim | level | what it means |
|---|---|---|
| Syntax compatibility | very high | A .ppy file is valid Python. The tooling, editors, and formatters that read Python read PPy. |
| Library compatibility | high, through plugins and boundaries | NumPy, PyTorch, JAX/Flax, pydantic, FastAPI, SciPy, pandas, PyArrow and the modeled stdlib work as-is. Everything else works behind an explicit ppy.dynamic boundary. |
| Semantic compatibility | intentionally incomplete | PPy does not aim to preserve arbitrary dynamic Python behavior. exec/eval, monkey-patching, dynamic namespace mutation, computed class construction, and unrestricted runtime reflection are restricted in exchange for reliable analysis, optimization, and native compilation. |
Running existing Python is a migration feature (ppy migrate). It is not the
definition of the language: a valid Python program is not necessarily a valid
PPy program.
Unknown, Any, and Dynamic¶
PPy keeps three kinds of missing type apart on purpose.
Unknown¶
Unknown is internal compiler state: inference has not resolved the value. It
must not survive strict compilation. It is reported (E1201, E1304) and is
not silently widened.
typing.Any¶
typing.Any is the permissive legacy spelling. It absorbs anything, and the
compiler polices nothing about it. Use it for interop annotations you already
trust.
ppy.Dynamic¶
ppy.Dynamic is the policed boundary. Any value may become Dynamic, but a
Dynamic value fits only Dynamic, Any, or object. Crossing into typed
code (a typed return, parameter, field, or declared variable) is E1508
until the value passes through ppy.check[T](value) or
ppy.assume[T](value).
ppy.check and ppy.assume¶
ppy.check[T](value) validates each runtime-checkable part of T,
recursively. It raises TypeError where the value falls short, and hands
back a value typed as T. It checks:
- a
list[int]element by element - a dataclass field by field
- an
i8's range - an
Array[int, 3]'s length - a
Buffer[float]'s format - a
Range,Length,Shape,DType, orContiguousrefinement against the value's own metadata
A T that PPy cannot validate soundly at runtime is rejected, never checked
in part. That includes:
- a callable
- an iterator
- a protocol,
@runtime_checkableor not.isinstanceagainst one answers whether the attributes are there and nothing of what they take or answer, so a class whosef(self)returns a string satisfies a protocol declaringf(self, x: int) -> int. - a contract between caller and callee such as
Owned[T],Borrowed[T],Mut[T], orNoAlias, which no single value can bear witness to
ppy.assume[T](value) performs no runtime validation. It is an explicit
unchecked assertion the programmer takes responsibility for, and it should
look like one.
Both differ from typing.cast in behavior:
castasserts and checks nothing.checkchecks and asserts nothing.assumeasserts and says so.
Examples: Dynamic boundaries, Narrowing, Classes.