A multi-module project¶
Two modules analyzed as one call graph and built as one program.
src/app.ppy calls geometry.perimeter from src/geometry.ppy. ppy check
src types both together, and ppy build links both modules' IR into one
object.
Run it¶
What it prints¶
ppy check src
(prints nothing; exits 0)
ppy run src/app.ppy
One graph¶
@ppy.pure
def perimeter(points: list[tuple[float, float]]) -> float:
total: float = 0.0
for index in range(len(points)):
a = points[index]
b = points[(index + 1) % len(points)]
total += distance(a[0], a[1], b[0], b[1])
return total
A function's parameter types may come from call sites in another file.
distance takes four f64, and perimeter calls it from a loop over
tuples. Both lower, and after linking the call is a direct native call. The
whole-program pass:
- inlines a small callee across modules,
- makes functions Python never binds private,
- drops what nothing reaches.
Cache keys and layout¶
A change to geometry.ppy invalidates app.ppy's cache entry, and nothing
else, because the dependency digest is part of its key.
pyproject.tomlat the root namessrcas the source root.app.ppycallsppy.install()before importing its sibling, so the same file runs under plainpythontoo.ppy convert src/andppy check src/take the directory and work across it.
Where the code comes from¶
src/app.ppy and src/geometry.ppy are hand-written; there is no .py
source and no conversion step.
Read on: Interop · The IR: linking and the program · Configuration
26_project/src/app.ppy¶
import ppy
ppy.install()
import geometry
def main() -> None:
square: list[tuple[float, float]] = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]
print(round(geometry.perimeter(square), 6))
print(round(geometry.distance(0.0, 0.0, 3.0, 4.0), 6))
if __name__ == "__main__":
main()
26_project/src/geometry.ppy¶
import ppy
from ppy import f64
@ppy.pure
@ppy.opt(3)
def distance(x1: f64, y1: f64, x2: f64, y2: f64) -> f64:
dx: f64 = x2 - x1
dy: f64 = y2 - y1
return (dx * dx + dy * dy) ** 0.5
@ppy.pure
def perimeter(points: list[tuple[float, float]]) -> float:
total: float = 0.0
for index in range(len(points)):
a = points[index]
b = points[(index + 1) % len(points)]
total += distance(a[0], a[1], b[0], b[1])
return total
Source: examples/26_project.