Repository navigation
Releases: rotnov/pycc-next
Release list
pycc-next v0.7.0
Real-world Python: frameworks, Windows. All five v0.7 goals are met. Windows x86_64 is a
first-class platform: CI runs the test suite, the corpus gate and the real-package wheel
checks on windows-2025, and the release ships a smoke-tested
pycc-v0.7.0-x86_64-windows.zip (first shipped, ahead of schedule, in 0.6.0).
--non-strict compiles classes inheriting library classes and sends only the smallest
enclosing function or method of a construct it cannot compile to CPython (also in 0.6.0).
pydantic 2.13.5 builds --ext --non-strict with 809 of its 1249 functions native and
5742 of its tests passing (5762 against its sources) -- but the compiled tree is
slower than CPython: import 0.40x, model definition 0.70x, model_json_schema()
0.70x, round-trips 0.91x of CPython's speed (the profile is in docs/ROADMAP.md, v0.7
accept 4; making it faster is v0.8). The real-world corpus goal carried over from v0.6 is
met: strict 190/200, --non-strict 200/200 with 0 of 1191 top-level statements in
CPython. Prebuilt archives: Linux x86_64, Linux aarch64, macOS arm64, Windows x86_64.
Benchmarks (python3 bench/run.py, best of 7, shared 4-core Xeon 2.1 GHz, Linux x86_64;
process start included except for the numpy kernels):
| program | CPython | pycc-next | Rust | pycc vs CPython | pycc vs Rust |
|---|---|---|---|---|---|
| collections_bfs | 0.843s | 0.131s | 0.054s | 6.5x faster | 2.43x slower |
| fib | 0.243s | 0.013s | 0.008s | 18.5x faster | 1.73x slower |
| mandelbrot | 1.485s | 0.042s | 0.040s | 35.2x faster | 1.04x slower |
| nbody | 2.299s | 0.068s | 0.027s | 33.6x faster | 2.55x slower |
| prefix_sum_np (kernel) | 2.746s | 0.030s | 0.014s | 92.3x faster | 2.15x slower |
| sieve | 1.084s | 0.058s | 0.022s | 18.7x faster | 2.66x slower |
| spectral_norm | 0.815s | 0.017s | 0.009s | 47.0x faster | 1.87x slower |
| stencil2d (kernel) | 3.694s | 0.007s | 0.003s | 532.5x faster | 2.25x slower |
Against 0.6.0's table pycc-next's own times are about the same except sieve (0.058s
against 0.048s); the ratios also moved with CPython's times on a shared machine, so treat
differences under ~20% as noise. Windows is not benchmarked.
Added
- Strict mode folds an
isinstance(x, C)test (Ca builtin class or a tuple of them,
combined withnot/and/or) whose subject's static type decides it, keeping only
the branch that runs; the subject is still evaluated (#5). An unannotated parameter
rebound to a value of another type gets a variable of its own, as a local does, and a
parameter anisinstanceguard normalizes (if not isinstance(B[0], list): B = [[b] for b in B]) gets one per path (#6). A nested helper whose calls give it conflicting
argument types is compiled once per call site (#7). Together they compile corpus
problem 188, and--non-strictruns no top-level statement of the corpus in CPython
(from 1 of 1191). rangeobjects kept in a variable or a list and only iterated (r = list(map(range, xs)),r[k] = range(a, b),for i in r[0]) compile, as their
(start, stop, step)triples;x is None/x is not Noneof a value whose type is
neverNone(a parameter defaulting toNonethat every call passes) folds like an
isinstancetest. Corpus problem 168 compiles: strict 190/200 (v0.7 accept 5).bench/pydantic_check.py --timetimes pydantic 2.13.5 compiled against its sources
(bench/pydantic_workload.py: import, model definition,model_json_schema(),
validation/serialization round-trips; fresh interleaved processes, best and median).
The one-off result (v0.7 accept 4): the compiled tree is slower -- import 0.40x,
definition 0.70x, schema 0.70x, round-trips 0.91x of CPython's speed -- with 5742
tests passing and 20 failing; the profile is recorded indocs/ROADMAP.md.
Changed
- A CPython exception caught in compiled code is mapped to its class by identity first
(the common case: CPython raised the builtin class itself), so catching aKeyError
from adicttakes 312 instead of 496 ns (CPython: 202).
Fixed
- Replacing an element of a list of
int/float/booltuples (rows[i] = (a, b)) leaked
the boxed tuple it overwrote. - The constraint solver typed
[[c] * b for i in range(a)]over a parametercas
list[int](a one-element display of a not-yet-typed value gave no type, and the
comprehension's was guessed before the operator settled), so a helper building one
was refused withT0022. list(range(10 ** 30))(andtuple()/sorted()of a range longer than a C
ssize_t) iterated forever; it now raises CPython'sOverflowError: Python int too large to convert to C ssize_t(#3).- A missing key of a
dict[..., str], an out-of-range index of alist[str]and
pop()of an empty one leaked the placeholderstrreturned beside the exception; it
is now the shared immortal emptystr(#4).
Known gaps
- Compiled library code is slower than CPython: pydantic 2.13.5 under
--ext --non-strictimports at 0.40x, defines models and builds JSON schemas at 0.70x and
round-trips at 0.91x of CPython's speed (fallback source compiled withcompile()at
every import, ~25-40 ns per call across the boundary, object-protocol bodies without
CPython's specializing interpreter). 440 of its 1249 functions still run in CPython
(top reasons: the...literal, function-localimport, nesteddef,lambda,
hash()of an object); 20 of its tests fail against the compiled tree (sys._getframe
walks from compiled frames, monkeypatched module globals compiled code does not see).
Target of v0.8. - Real-world corpus, strict: 10 of 200 refused -- mixed-type containers, an
int | None
used as anintwithout aNonetest, a variable rebound to another type on some
paths, iterator objects as values. - Windows x86_64: a native program's stdout is
\n-terminated UTF-8 whatever the console
code page,--static-libpythonis refused, and the compiler uses the system allocator. - Open rows of
docs/DEVIATIONS.md, among them: anintbeyond 63 bits as a slice bound
andint ** negative int; container element and key types strict mode refuses
(T0034-T0039); unsupported constructs refused in strict mode (C0001) or run in
CPython under--non-strict;sys._getframecounts only Python frames; a published
native class is not subclassable from Python outside its package;async withruns in
CPython; an uncaught exception in compiled code prints only the last traceback line;
raise X from Nonekeeps__suppress_context__false; reference cycles are never freed
and__del__is never called; a bigintheld by a tuple is never released; enum
members at the--extboundary (C0003). Seedocs/DEVIATIONS.mdfor every known
difference from CPython 3.13.
pycc-next v0.6.0
Drop-in for real projects. pip install builds a project with pycc through a PEP 517
backend shipped in the release archive; strict mode gets a real int | float type with
CPython's semantics for every operation; a differential fuzzer compares generated
programs against CPython in four build modes (0 findings in its last 30-minute nightly
run, 18 mismatches found and fixed on the way); untyped classes compile under
--non-strict, so pypng decodes the PngSuite images its tests use 3.18x faster than
CPython (v0.5: ~1.6x). Also shipped, ahead of v0.7: a Windows x86_64 archive (MSVC
toolchain, abi3 .pyd modules, win_amd64 wheels), method-level fallback (only the
smallest enclosing function or method of a construct pycc cannot compile runs in
CPython) and classes inheriting library classes, which take pydantic 2.13.5 to 789 of its
1249 functions compiled with 5716 of its 5740 tests passing (its speed is not measured
yet), and pypng to 78 of 85. Not met: the real-world corpus target (ROADMAP v0.6 accept
5) -- strict compiles 188/200 (target 190) and --non-strict keeps 1 of 1191 top-level
statements in CPython (target 0); carried over to v0.7. Prebuilt archives: Linux x86_64,
Linux aarch64, macOS arm64 and, new, Windows x86_64.
Benchmarks (python3 bench/run.py, best of 7, shared 4-core Xeon 2.1 GHz, load average
6-11 from other jobs; process start included except for the numpy kernels):
| program | CPython | pycc-next | Rust | pycc vs CPython | pycc vs Rust |
|---|---|---|---|---|---|
| collections_bfs | 0.900s | 0.144s | 0.049s | 6.3x faster | 2.92x slower |
| fib | 0.282s | 0.013s | 0.007s | 21.7x faster | 1.91x slower |
| mandelbrot | 1.545s | 0.043s | 0.042s | 35.5x faster | 1.03x slower |
| nbody | 2.530s | 0.067s | 0.025s | 37.7x faster | 2.68x slower |
| prefix_sum_np (kernel) | 2.798s | 0.029s | 0.013s | 95.4x faster | 2.19x slower |
| sieve | 1.000s | 0.048s | 0.021s | 20.7x faster | 2.35x slower |
| spectral_norm | 0.836s | 0.018s | 0.011s | 45.5x faster | 1.69x slower |
| stencil2d (kernel) | 3.751s | 0.007s | 0.003s | 530.5x faster | 2.17x slower |
Against 0.5.0's table (same machine type, different load): fib, sieve and the numpy
kernels read lower (fib 21.7x against 26.5x, sieve 20.7x against 37.5x, prefix_sum_np
95x against 135x), nbody and mandelbrot higher; this run shared the box with other
builds, so treat the differences as noise until re-measured on a quiet machine.
Added
-
Windows x86_64 (v0.7 accept 1):
pycc-vX.Y.Z-x86_64-windows.zip(bin\pycc.exe,
lib\pycc\pycc_rt.lib, the backend's wheel) built, packaged and smoke-tested by the
release workflow onwindows-2025with the MSVC toolchain, and verified by its publish
job with the other archives. Native programs, abi3.pydextension modules (linked
againstpython3.lib, CPython 3.13+) and programs embedding CPython;pycc.build
tags wheelscp313-abi3-win_amd64. pycc driveslink.exethroughclang.exe
(PYCC_CLANG,PATH, Visual Studio's clang component, or the LLVM installer's);
PYCC_PYTHONdefaults topython.exe. CI runs the test suite, the non-strict corpus
gate and the real-package checks (bench/package_check.py,bench/wheel_check.py) on
windows-2025; LLVM is LLVM's own 22.1.8 Windows release, pinned by sha256
(scripts/windows_llvm.sh). The memory tests measure the peak working set there. -
macOS CI also builds the real packages into wheels with
pip wheel
(bench/wheel_check.py). -
Method-level fallback (v0.7 accept 3): under
--non-strict, a construct pycc cannot
compile sends only the smallest enclosing function or method to CPython, not its
whole class or module statement. A class statement CPython runs keeps each method's
body compiled (itsdefforwards to a compiled function;selfcrosses as the
instance,super(),__class__and private-name mangling as in CPython); a
decorated top-level function keeps itsdefin CPython and its body compiled; in an
--extmodule a compiled class keeps its native type, a method that cannot compile
attached to it as a CPython function (src/fallback/lift.rs,src/fallback/attach.rs,
--report-fallbacksreports each). -
Classes from libraries (v0.7 accept 2):
--non-strictcompiles the methods of classes
inheriting CPython classes (threading.Thread,collections.Counter,
argparse.Action, anamedtuple, library exceptions,class M(lib.Base)), with
metaclasses,__init_subclass__, descriptors, class attributes of any type,
@property,@staticmethod,@classmethod,functools.cached_propertyand
functools.wraps-style decorators, all applied by CPython; annotations naming
library types (re.Match,mod.Type), general unions andX | Noneof objects
(objects); thewithstatement (PEP 343;async withstill runs in CPython), and
starred expressions in displays, assignment targets and calls. A compiled generator
handed to CPython supportssend(None),throw()andclose()(as
contextlib.contextmanagerneeds). A class Python code in the package subclasses
(its own fallback code or a sibling module) is created by CPython, since a published
native class is not subclassable; private module functions are published by
non-strict--extmodules, so sibling modules can import them; an--extfunction
whose signature the boundary cannot bind (**kwargs, positional-only parameters of a
public function) runs in CPython.from __future__ import annotations as xand
augmented assignment to a computed target (f()[k] += 1) compile. -
bench/pydantic_check.pybuilds pydantic 2.13.5's Python part (pinned by sha256)
module by module with--ext --non-strict, counts the functions that compile, and
optionally runs pydantic's own test suite against the compiled tree: 789 of its 1249
functions compile and every module builds (33 and 55 modules failing to build
before this release's work); 5716 of its tests pass, 24 fail (monkeypatched module
attributes compiled code does not see,sys._getframefrom compiled frames, and
four subprocess tests that fail in CPython too). -
pip installbuilds with pycc (v0.6 accept 1):pycc.build, a PEP 517 (and PEP 660)
build backend inpython/, shipped in the release archive as
python/pycc_build-X.Y.Z-py3-none-any.whl. A project selecting it compiles each of
its modules withpycc build --ext(strict by default;[tool.pycc]sets
--non-strict, per-module overrides, modules kept in Python, modules allowed to fall
back to Python, package discovery forsrc/and flat layouts, extra pycc flags) and
gets acp313-abi3-<platform>wheel (manylinux_<glibc>_<arch>, orlinux_<arch>when
a module links a library manylinux does not allow;macosx_<N>_0_arm64) with METADATA
from static PEP 621 metadata, entry points and a sha256 RECORD; also sdists and
editable installs (uncompiled). A module pycc refuses fails the build with pycc's
diagnostics. It finds the compiler as$PYCCorpycconPATH, needs no network,
and runs no project code. README: "Use pycc in your project". -
CI builds diff-match-patch, tomli and pypng into wheels with
pip wheeland the
release archive's backend (bench/wheel_check.py, Linux x86_64 and aarch64), installs
each into a fresh venv and runs the package's own test suite against it; the release
smoke test (scripts/smoke.sh, all three platforms) builds and runs a small project's
wheel the same way. -
A differential fuzzer (v0.6 accept 4,
fuzz/): generated typed programs run under
CPython 3.13 and as native strict,--non-strict,--extand--ext --non-strict
builds; stdout, the uncaught exception and the exit status must agree. Refusals are
counted per feature and mode, compiler crashes and mismatches are findings, each
reduced to a minimal program. CI runs it for 2 minutes per push and 30 minutes
nightly (.github/workflows/fuzz.yml, findings uploaded as an artifact). -
Untyped code compiled natively by
--non-strict(ROADMAP v0.6 accept 2, in progress):bytesliterals, as CPythonbytesobjects (strict mode still refuses them,C0001);- calls unpacking arguments into a CPython callee --
f(*args),o.m(a, *xs, b, k=v, **kw),zip(*rows)-- bound at run time with CPython's checks andTypeError
messages; a builtin called with keyword or unpacked arguments is CPython's own; exceptclauses whose type is a CPython object (except struct.error:,except mod.Error as e:,ethe CPython exception), tested in order with the other clauses;- an object tested against a tuple, list or set of scalars (
kind in (1, 2, 3, 4)); - a standard-library function pycc compiles (
math.ceil, ...) called with a CPython
object is CPython's own; - builtin exception and warning classes,
bytes,bytearray,getattr, ... as values
(warnings.warn(msg, RuntimeWarning)); - a builtin call inside an
--extmodule's public generator is routed through CPython
like one anywhere else (its diagnostic is now placed); - untyped classes: attributes a method creates outside
__init__(self.width = ...
in a header parser) areobjectslots, an unset one raising CPython's
AttributeError; a method's keyword calls of its own methods (self.chunk(lenient= True)), generator methods, a method read as a value (self._scaled(self.size, k)),
nested functions defined under anif(each branch'sdef newarray()), slice stores
on objects, and private methods CPython code looks up (getattr(self, "_process_" + kind)), published in non-strict mode when their parameters are all objects; - a module-level function used as a value crosses into CPython as a callable (
fn = (None, sub, up)[k]; fn(...)), one object per function; - a helper parameter defaulting to
Noneand called with anintis an optional
int; one rebound to an object is an ...
pycc-next v0.5.0
Typed packages at native speed. Tuples of any element type, including CPython objects,
are native (tuple[T, ...], tuple[int, Any]); runtime free lists cut allocations; a
third real package, pypng, decodes PNGs 12-15x faster than CPython with its own suite
passing; builds are 5x faster (diff-match-patch 25 s -> 5 s on CI); strict mode compiles
181/200 real-world programs unchanged and --non-strict runs all but 2 of their 1191
top-level statements natively. CI now gates package build time and hot-path instruction
counts. tomli stays at ~1.5x CPython: half of its loads() runs inside CPython's regex
engine and dict operations, which a compiler cannot take over without breaking aliasing
(see ROADMAP v0.5 accept 1).
Benchmarks (python3 bench/run.py, best of 7, shared 4-core box): fib 26.5x faster than
CPython / 1.7x of Rust, mandelbrot 34.6x / 1.0x, nbody 32.8x / 2.5x, sieve 37.5x / 2.0x,
spectral_norm ~66x / 2.0x, collections_bfs 6.9x / 3.0x, numpy kernels stencil2d 590x /
2.3x and prefix_sum_np 135x / 1.6x. Real packages (bench/package_check.py --time):
diff-match-patch 12.4x, pypng 15.2x, tomli 1.56x.
Added
-
Real-world corpus (v0.5 accept 3):
python3 bench/corpus.py --flags=--non-strict
passes 200/200 with 0 wrong outputs and 2 of 1191 top-level statements in CPython
fallback (v0.4: 8), both listed bybench/corpus.py --show-fallbacksand explained in
the README's corpus note; strict mode passes 181/200 (171 before). What it took:- strict inference: a tuple assignment of values that can observe none of its targets
(a, b = [], set(),m, seen = 0, {0: None}) binds each name on its own, so its
empty containers are typed by their own uses;f(d[k])types an unannotated parameter
from the dict's value type; an unannotated__init__parameter is typed from an
argument read from an unannotated function's return (N, K = MAP()) or from a
comprehension's loop variable ([Node(i) for i in range(n)]); a call-site
parameter some call cannot type yet waits until it can (a later call may disagree);
a global the module binds only inside a loop types the functions reading it; "literal".format(...)with{}/{0}fields,!r/!sand a constant spec
compiles natively (str()/repr()/format()concatenated);float('inf')held by a variable anintlater replaces (best = float('inf')
...best = min(best, cost)) is anintinfinity: ordering, equality,min/max,
printing,hash,float(),+/-/*by anintand/answer as CPython's
float infinity does;min(s)/max(s)of astr;- non-strict: CPython's own builtins over CPython objects --
range(...)as a value,
tuple(xs),iterof a list display,next,list,sorted,sum,min/max
of one object or of two values,enumerate,itertools.accumulate/chain; a
generator that yields native values beside objects yields objects;return None
beside a value of another type returns an object; a tuple display holdingNoneis
an object to the solver as to the checker; a fresh set on the right of|,&,
-,^on an object crosses as a copy;set(xs)is a fresh container; a value of
another type stored into a list ofNones (or a dict looked up with an object key)
makes only that container CPython's, not the classes of the values flowing into it.
- strict inference: a tuple assignment of values that can observe none of its targets
-
bench/corpus.py --only 017,077runs a subset;--show-fallbackslists the
problems with statements in CPython. -
bench/package_check.py --instructionscounts, under callgrind, the instructions one
call of each real package's hot path executes against the compiled modules and fails
a package over its documented bound, 5% above its count on CI's Linux x86_64 runner:
diff-match-patch'sdiff_mainof 2000 characters 133.3M (126.9M), tomli'sloads()
2.93M (2.79M; 2.53M with Ubuntu's python3.13, v0.5 accept 1's method), pypng's
read()22.2M (21.2M). CI runs it there: unlike wall time, the count does not vary
between runs of a shared runner. -
Compile speed (v0.5 accept 4): building diff-match-patch takes 5.2 s on the CI Linux
runner (v0.4: ~25 s), tomli 2.8 s and pypng 1.0 s, and everybench/pyprogram
builds in under 1 s; CI fails a package over 10 s or a benchmark build over 2 s
(bench/package_check.pyprints each module's build time,bench/run.py --build-onlyeach program's). The non-strict specialization loop runs independent
attempts on threads (the program without specializations beside the first attempt,
every suspect of a span-less failure alone at once) and a failing check runs once,
not twice; the type checker hashes with FxHash and the compiler allocates with
mimalloc; a large optimized module is emitted as several objects by LLVM's backend
on several threads; the--extshim and an embedded build's launcher and sidecar
are compiled and assembled while LLVM runs.PYCC_TIME=1prints per-phase timings. -
A third real package (v0.5 accept 2):
bench/package_check.pyadds
pypng 0.20220715.0 (MIT, a pure-Python PNG codec,
sdist pinned by sha256), a single module (the table now takes one as well as a
package).pngbuilds unchanged with--ext --non-strict, its own 103 tests pass
against it (CI) and reading a 256x256 RGB image that uses all five PNG filters runs
10x faster than in CPython (python3 bench/package_check.py pypng --time --runs 5:
0.015 s vs 0.157 s); 15 of its 85 functions compile, itsReader/Writerclasses
still run in CPython. -
Untyped byte loops run native: a guarded specialization also guesses
bytearrayfor a
parameter whose items are used as numbers ((x + a) & 0xffwithx = s[i], an index
local computed from anintparameter counts as its use too) and takes the caller's
bytearrayas it is: items are read and stored in its own buffer, its size and buffer
pointer held in registers until something may run CPython code (then re-read), with
bytearray's ownIndexError/ValueErrors (the stored byte is checked before the
index, as CPython does). pypng'sundo_filter_*loops run 25x-50x faster per byte. -
abs()of a smallintis computed inline instead of calling the runtime. -
Native variadic tuples (v0.5 accept 1, partly):
tuple[T, ...]ofstr, class
instances or containers is a native tuple -- indexing, slicing,+,*,in,
comparison, hashing, iteration, unpacking, dict keys and set elements without the C
API -- and a fixed tuple of matching elements is one with no conversion. It crosses
into CPython as a copy made once and remembered, sof(t) is tholds for a tuple that
came from CPython; a strict--exttuple[str, ...]parameter takes atupleof
str(anything else raisesTypeError), and a variadic tuple of mutable elements is
refused at the boundary (C0003) rather than copied.tuple[int, ...]and the other
scalar ones areT0053in strict mode (not implemented yet). -
Native tuples of objects: under
--non-strictand at the--extboundary,
tuple[int, Any]keeps its position native and its value the CPython object itself,
and a native tuple returned where a tuple with object slots is declared is converted
element by element (return pos, Truefrom-> tuple[int, Any]).
Fixed
- The non-strict object stdlib shim passed a one-argument call's tuple argument as the
argument tuple (heapq.heapify(t)of a CPython tuple raisedTypeError). - The
C0001for an unsupported.get()call no longer says it needs exactly two
arguments: it names bothdict.get(key)anddict.get(key, default), and what the
one-argument form takes. - Compiled callers see every rebinding of a module-level function (#1): a
module-scope binding of a name adefalso binds (f = g, a loop orwith
target, ...) is refused withC0001(in non-strict mode it runs in CPython, with the
defs of the name, so compiled code reads the name from the module's namespace); a
defor lambda that runs in CPython fallback and rebinds a compiled function's name
(in a block, throughglobal) makes itsdefs run there too; a value of a
function defined more than once (g = f, then anotherdef f) keeps the
definition of that moment. Before, compiled callers kept calling the old function or
raisedNameError. - A guarded specialization calls a builtin its original reaches through CPython (
abs
of an object) natively when its typed arguments allow (and as the original does when
they do not), instead of always through CPython. - A function was not specialized when its original failed to compile in a way reported
without a source span in the specialized attempt (a module with no imports and an
abs()of an object): such a failure is the original's, not the copy's. - A string index or slice that raised (
s[0]of"",s[::0]) leaked its 72-byte
placeholder result; a list or variadic tuple sliced with a zero step leaked an empty
list.
Performance
- Small runtime objects are recycled through free lists, as CPython does for tuples:
list/tupleheaders, the data blocks of tuples of 1 to 8 elements (one list per
length) andstrobjects are taken from and returned to bounded pools
(crates/pycc_rt/src/pool.rs) instead ofmalloc/free, and slicing or extending a
tuple writes its words and kinds straight into its data block (no temporary
vectors). tomli's keys are nativetuple[str, ...]values;loads()of its
benchmark document takes 2.54M instructions per call under callgrind (v0.4: 2.60M;
2.70M with native tuples before the pools; CPython: 4.56M), meeting v0.5 accept 1 as
amended (seedocs/ROADMAP.md). - Objects keep every jump within a 32-byte boundary on x86 (LLVM's
-x86-branches-within-32B-boundaries, padding with instruction prefixes where it
can): Skylake-derived Intel cores (through Cascade Lake, common among...
pycc-next v0.4.0
Fast untyped code, no leaks. --non-strict keeps untyped code native: strings index in
O(1), names widen per function, and public untyped --ext functions get guarded native
copies, so diff-match-patch's own speed test runs 11.5x faster than CPython (v0.3:
1.7x). A second real package, tomli (the parser behind tomllib), builds unchanged and
passes its own suite, 1.67x faster than CPython. Exceptions are reference counted and
freed. Strict mode compiles 171/200 real-world programs unchanged (v0.3: 161), and
--non-strict runs all but 8 of their 1191 top-level statements natively (v0.3: 23).
Circular imports, IntEnum as int, sorting class instances and dataclasses.fields
/ asdict work as in CPython.
Benchmarks (python3 bench/run.py, best of 7, shared 4-core box): fib 18.9x faster than
CPython / 2.2x of Rust, mandelbrot 35.2x / 0.98x, nbody 32.8x / 2.6x, sieve 21.3x /
2.5x, spectral_norm 47.5x / 1.8x, collections_bfs 6.5x / 2.7x, numpy kernels stencil2d
459x / 2.8x and prefix_sum_np 95x / 2.1x.
Added
- Untyped code runs native (v0.4 accept 1): an
--ext --non-strictmodule's unannotated
function or method that loops gets a guarded native specialization -- a copy whose
parameters are typedstrorintfrom how its body uses them, called when the
arguments are exactly those types (any other call runs on the CPython objects as
before; a copy the checker refuses is dropped, never fallen back). diff-match-patch's
own speed test now runs 12x faster than CPython (1.03 s vs 12.3 s) (v0.3: 1.7x), its suite passing
(python3 bench/package_check.py --time).--report-fallbackslistsspecialized:
andnot specialized:functions. - A second real package (v0.4 accept 4):
bench/package_check.pyis a table of pinned
packages and adds tomli 2.5.0 (MIT, the TOML parser
tomllibcomes from; GitHub tag archive pinned by its sha256 and that of the files used). Its
four modules build unchanged with--ext --non-strict --foreign-relative-imports, its
own test suite passes against them (CI) and 36 of 40 functions compile;loads()runs
1.6x faster than in CPython (python3 bench/package_check.py tomli --time --runs 15,
best of 15 runs of 100 calls on one machine: 0.036 s vs 0.059 s, 1.52x-1.75x over seven
runs; was 0.40x). What typed packages written for mypyc needed:from __future__ import annotations: an annotation pycc cannot lower is erased
instead of making its statement fall back, and fallback code is compiled with the
flag; names imported underif TYPE_CHECKING:from a sibling module that are
aliases (Pos = int,Key = tuple[str, ...]) are read through to their definition.- A function whose signature the
--extboundary cannot carry (tuple[int, str]
result,frozensetparameter) is compiled for the module's callers and runs as its
CPython twin when Python calls it (twin:in--report-fallbacks). - Non-strict signature wrappers: defaults of unannotated or object parameters, and a
Python caller passing a value of another type than a scalar parameter's runs the
function's CPython twin, so its own error handling answers (loads(b"")raises
tomli'sTypeError, not the boundary's). - A module function with an
Iterable[T]parameter gets afrozenset[T]
specialization that the module's own calls with a native frozenset run. - Native
frozenset(str),frozenset(chr(i) for i in range(n))(achrwhose
argument is provably not a surrogate is native in non-strict mode too), set
displays in membership tests,isdisjoint/issuperset/issubsetwith astr,
str.startswith(p, start),find/index(sub, start). - Non-strict:
isinstance(o, (A, B))of an object,repr(o),raise o from cause,
type(native), an object rebinding anint/float/boolparameter (converted),
an object passed for afrozenset[str]/frozenset[int]parameter (copied), a
tuple display returned astuple[...], an object in a native frozenset, atry
and its handler binding a name with two types,Callable[...],Iterable[...]
andtuple[X, ...]annotations as objects,self.x = {}as an object attribute.
- Faster code around CPython objects and tuples (v0.4 accept 4):
- Multi-value returns: a function whose every
returnis a tuple display of one
arity, or forwards another such function, gets a compiled twin that returns the
values without a tuple, and a caller that unpacks the call (pos, value = f(...))
calls it -- no CPython tuple (nor pycc tuple, for a nativetuple[int, str]) is
built, unpacked and freed. Result types are what the returns agree on, an object
where they differ; the function itself serves every other use, and a call whose
name may be rebound runs it (tests/multi_return.rs). a, b = x, ywith object values binds the targets without building two tuples
(tests/object_swap_unpack.rs).- Non-strict: a
str | Nonelocal bound only to nativestrvalues andNone
(char: str | None = src[pos]under postponed annotations) stays a nativestr,
null standing forNone; its literal comparisons,frozenset[str]membership and
is Nonetests are native and any other use sees the object it stands for
(tests/optional_str_local.rs). while s[i] in chars: i += 1(ornot in) over a set ofstris one scan of the
text, theIndexErrorof running off either end raised as the loop raises it
(tests/str_scan_loop.rs).- In a condition, an object compared with a
strliteral, or tested against a native
frozenset ofstr, is compared by its UTF-8 bytes when it is an exactstr, with
nostrmade for the literal and no CPython copy of the set; anything else takes
CPython's own operation (tests/object_text_compare.rs). - Non-strict object operations take shorter paths:
o[i]ando[a:b]with native
intkeys read an exact tuple or list directly, a tuple display of objects is
built as a tuple,o.m(args)with literal or parameter arguments is one
PyObject_VectorcallMethod, a rich comparison in anif/whileanswers a native
bool(two exactstroperands are compared without a result object), and
isinstanceof a plain built-in value skips the__class__lookup
(tests/object_fast_paths.rs). - A string literal reads its site's cached, immortal
strwith no call; freeing a
list or an instance no longer allocates; error paths are laid out of line; the
runtime builds as one codegen unit.
- Multi-value returns: a function whose every
- Crossing to CPython is cheaper: a native
stror frozenset converted to a CPython
object keeps that object and shares it on its next crossing (astra Python caller
passed in shares the caller's);strliterals are built once per site; attribute
names are interned once; the C shim is compiled with-O2; the pending-exception test
after each runtime call no longer reads thread-local storage while no thread has an
exception pending;c in frozenset_of_strfor one ASCII character is a bit test. - Circular imports between project modules run as in CPython (v0.4 accept 5): a module
imported back sees the partially initialized one -- the names it bound before the
import -- andfrom m import xof a namemhas not bound yet raises CPython's
ImportErrorwith its message. Only the entry module imported back is still refused
(E0108, now "import cycle through the entry module"). - Language gaps from v0.3 (v0.4 accept 5):
IntEnummembers are usable where
anintis needed (arithmetic, comparisons,range(), indexing,intparameters);
sorted(),min(),max()andlist.sort()of class instances order by__lt__
(or__gt__, or@dataclass(order=True)), also withkey=returning instances and
reverse=; a list changed while it is sorted raisesValueError: list modified during sortas in CPython;x in sfor a set of instances uses__hash__/__eq__;
dataclasses.fields(),asdict(),astuple()andreplace()compile natively
(asdict()of mixed field types needs--non-strict); printing a list or tuple of
instances uses their__repr__; a strict--extmodule publishes its native
module-level variables (numbers, strings, tuples of them) as attributes, kept current
when a function rebinds them withglobal. - Format specs and printf-style formatting compile natively:
f"{x:>10,.2f}",
format(x, spec)and"%5d|%-8.3f|%s" % (...)overint,bool,floatandstr
follow CPython's format-spec mini-language and%conversions exactly, errors
included (pycc_rt::format;tests/format_spec.rs). A conversion CPython refuses
for its argument's type ('%x' % 1.5) is a compile-timeT0021in strict mode; a
spec with a nested field ({x:{w}}) is stillC0001. - Strict mode admits a read of a name bound on some paths only: it is checked at run
time and raises CPython'sUnboundLocalError/NameErrorwhere CPython does
(tests/maybe_unbound_runtime.rs).T0041stays for a name CPython would read
something else for when unbound (a builtin,__name__), adel/except ... as
name, and a buffer. - Non-strict: a comprehension over a CPython object whose elements are native lists,
sets, dicts or tuples collects a native list;set(o)of an object and an empty
set()/dict()the empty-container pass cannot type are CPython's own; a name
rebound from one container kind to another (t = [..], thent = {..}on some
path) becomes an object;t |= {..}on such a name and aforover a maybe-bound
object run through CPython (tests/non_strict_object_containers.rs). d.get(k)with no default compiles (asd[k] if k in d else None, for a key that is a
name, a literal or a subscript of those): anOptionalof the value type. An
Optional[int|float|bool]compares with== None/!= None(narrowing like
`is Non...
pycc-next v0.3.0
Packages, stdlib, everyday code. Programs of several modules and packages compile as
one unit; dataclasses, enums, functools.cache, deque/Counter/defaultdict and
itertools compile natively; a real PyPI package (diff-match-patch) builds unchanged
as --ext modules and passes its own test suite in CI; strict mode compiles 161/200
real-world programs unchanged (was 124) and --non-strict runs 98.1% of their
top-level statements natively (was 95.1%). Prebuilt archives for Linux x86_64, Linux
aarch64 and macOS 26+ on Apple silicon.
Benchmarks (python3 bench/run.py, best of 7, shared 4-core box): fib 19.9x faster
than CPython / 2.0x of Rust, mandelbrot 43.9x / 0.96x, nbody 29.4x / 2.6x, sieve 25.0x
/ 1.9x, spectral_norm 46.1x / 1.7x, collections_bfs 6.8x / 2.9x, numpy kernels
stencil2d 677x / 2.3x and prefix_sum_np 78x / 1.7x.
Added
- A real PyPI package, unchanged:
bench/package_check.py(CI-gated on Linux x86_64)
builds every module of diff-match-patch 20241021 with--ext --non-strictand runs the
package's own test suite with CPython against the compiled modules; every function
and method compiles (v0.3 accept 4). --ext: a package's__init__.pybuilds topkg/__init__.abi3.so(-o pkg/__init__),
imported as the package; under--foreign-relative-importsa module's__name__is
its dotted name (pkg.mod); the module docstring is__doc__; a class's literal
constants (INSERT = 1) are attributes of its published type.- Non-strict
--ext: module-level variables and imports are module attributes
(mod.X,from mod import X, a package__init__'s re-exports). - Non-strict:
o[a:b] = von a CPython object; a class attribute initialized with any
expression (PATTERN = re.compile(...)), evaluated once where the class runs; a
method that can run off its end returnsNone; an in-module call fills anobject
parameter'sNoneor scalar default; a builtin exception built from an object message
(ValueError("bad: " + obj)). - Non-strict typing: an instance attribute nothing types is an object; an attribute
stored values of two types becomes an object; a local first bound toNoneand later
to something else is an object; a helper parameter or return inferred both as an
object and as a scalar is an object; a helper returning a tuple orNonereturns an
object; a container held by a name that flows into an object tuple becomes an object. - Linux aarch64: a
pycc-vX.Y.Z-aarch64-linux.tar.gzarchive (glibc 2.39+), built,
tested and smoke-tested on GitHub'subuntu-24.04-armrunner next to the other two;
CI runs the full test suite and the corpus gate on Linux aarch64. itertoolsnatively in strict mode (and in--non-strictand--extcode), with no
embedded CPython:count,islice,chain,chain.from_iterable,accumulate
(operatorfunctions,max/min, two-argument lambdas of those,initial=),
pairwise,groupby(with akey),product(withrepeat=),permutations,
combinationsandcombinations_with_replacement. Each returns a lazy iterator
implemented in the runtime, consumed byfor,list(),sum(),next(),
comprehensions and every other generator consumer; inputs are lists,strs,ranges
and generators, pulled only as far as CPython pulls them.collections.dequenatively:deque(),deque(iterable[, maxlen]),append,
appendleft,pop,popleft,extend,extendleft,rotate, indexing,len,
in,index,count,remove, iteration,copy,+/*, equality andrepr
(deque([...], maxlen=n));deque[T]annotations. Both ends are O(1) amortized, and a
BFS over a deque runs ~10x faster than CPython.collections.Counterandcollections.defaultdictnatively:Counter(),
Counter(list|str|dict),c[k]reading0for a missing key,most_common([n]),
update,subtract,elements,total,+/-/|/&, equality and CPython's
reprorder;defaultdict(int|float|str|list|set|dict|lambda|function)with the
factory run on a missing-key read, and nested defaultdicts.Counter[K]and
defaultdict[K, V]annotations, anddefaultdict(list)typed from its uses.
bench/py/collections_bfs.py(a deque BFS plus aCounter) runs ~9x faster than
CPython.- Dict
|(union) between two dicts. - A list, set or dict comprehension over a generator (
[x for x in gen()]). - An unannotated generator helper whose yields the solver cannot type from their terms
alone (a yielded tuple display, a value read from a local container) is typed from its
body once its parameters are known; this includes every lifted generator expression. - Programs of several modules and packages compile as one unit (natively in strict and
--non-strictmode, and as an--extmodule):import m,import pkg.sub [as s],
from pkg.sub import f [as g], relative imports and__init__.py;m.f(...),
m.CONSTandm.Class(...)are direct native calls and reads. Each module has its own
namespace and__name__, and its body runs once at its first import, in CPython's
import order. An unresolved module or name, a module used as a value and an import
cycle are compile errors (T0021,C0001,E0108). dataclasses.dataclass, natively in strict mode:@dataclassand
@dataclass(...)withinit/repr/eq/order/frozen/unsafe_hash, field
defaults,field(default=..., default_factory=..., init=, repr=, compare=),
__post_init__, inherited fields, generated__init__/__repr__/__eq__/
ordering/__hash__, anddataclasses.FrozenInstanceErroron assignment to a frozen
field. An--extdataclass takes keyword arguments and prints as in CPython.enum:IntEnumbesideEnum/StrEnum; members print,str(),repr()and
format exactly as CPython's (Color.RED,<Color.RED: 1>,2for anIntEnum),
Color(value)looks a member up (ValueError: 4 is not a valid Color),case Color.RED:patterns, members as set elements and dict keys,IntEnumordering.functools.cache/functools.lru_cache(bare,(),maxsize=None,maxsize=N
with least-recently-used eviction,maxsize=0) on module-level functions with
int/float/bool/strparameters, andf.cache_clear(). Any other decorator
still runs in CPython under--non-strict.- Classes:
print/str()/f-strings call a class's__str__or__repr__, the
repr()builtin and f-string!r/!swork,==/!=/</<=/>/>=between
instances call the class's dunders,==without__eq__andiscompare identity,
and a call of a constructor with defaults may omit them.--extclasses install
__repr__/__str__astp_repr/tp_str. hash()of astr, afloatand a tuple holding them.- Strict mode infers more of real-world untyped code (the corpus, v0.3 accept 3):
- a return the solver cannot type from its terms (a returned tuple display such as
return up, down, a value computed from a local it did not type) is typed from the
function's body once its parameters are, and inference runs again with it, so its
callers and the parameters they feed are typed too; - an empty list whose producer reads an unannotated function's parameter or return
(result.append(x % num)) is typed once those are inferred; - an empty list bound to a name whose element nothing reads, produces or compares
(L = []only rebound, measured or printed) is alist[int]: no operation can
observe it; - an operator over two operands already typed is typed at once, so
a.count(m - 1)
types the parameter it is passed to.
- a return the solver cannot type from its terms (a returned tuple display such as
- The builtins
exit()/quit()andfrom sys import exitaresys.exit(run through
the embedded CPython, sofinallyblocks run and the exit status and message are
CPython's). - A module-level
X = sys.stdin/X = sys.stdoutalias:X.readline(),
X.write(s)and the other stream operations are the native ones. - More template idioms of competitive-programming code compile natively: a module-level
ifon a flag bound once to a literal (DEBUG = 0) or onsys.gettrace()is the
branch that runs;next(sys.stdin)(a line,StopIterationat EOF); a parameter
whose default is a builtin conversion no call passes (def rl(t=int));x in cof a
constant tuple;a, b = x, ytyping both targets for inference;
g[i].sort(reverse=True); adefreturningsys.stdin.buffer.readline()read only
as numbers. - Non-strict mode: the inference solver runs again with what it settled written in, and
a return it leaves open is typed from the body when everyreturnagrees, so less
code becomes a CPython object. - A tuple whose elements share one type:
t[k]with a computed index (int, bool and
float elements;IndexError: tuple index out of rangeoutside[-len, len)), and
for x in t/ a comprehension over it (any element a list can hold). - A generator expression reading a module-level loop variable (
all(q % p for p in primes)insidefor q in ...) is typed;graph[u].append(v)types the empty list
graph's items start as ({i: [] for i in range(n)}); non-strict mode runs
any/allof a generator of CPython objects natively. - Item access on an instance of a module class defining
__getitem__/__setitem__/
__delitem__(throughself, or a name bound only to calls of the class) calls those
methods, and their unannotated parameters are inferred from those calls. list()anddict()with no argument are[]and{};set.update(g)and
list.extend(g)take a generator; a local rebound from a scalar (temp = ord(c)) to an
empty list is a separate variable; non-strict mode passes a generator to a CPython
object's method as an iterator over it.- The real-world corpus (v0.3 accept 3): strict mode passes 161/200, non-strict mode runs
98.1% of top-level statements natively.
Changed
- Non-strict mode:
chr()runs in CPython, whose ...
pycc-next v0.2.0
Rust speed, numpy, more Python. Every benchmark is within 3x of hand-written Rust,
typed numpy arrays compile to native loads and stores, strict mode compiles 124/200
real-world programs unchanged (was 93), and --non-strict runs 95% of their top-level
statements natively (was 84%). Prebuilt archives for Linux x86_64 and macOS 26+ on
Apple silicon.
Added
-
Typed numpy arrays:
numpy.ndarray/NDArray[float64|int64|bool]of 1 and 2
dimensions are native in strict and--extcode: element reads and writes,.shape,
len, iteration,numpy.zeros/ones/empty/array, with no CPython call in the loop
body. Benchmarksstencil2d(436x faster than CPython, 2.5x of Rust) and
prefix_sum_np(80x, 1.8x). -
macOS arm64: a
pycc-vX.Y.Z-aarch64-macos.tar.gzarchive (macOS 26.0+, Apple
silicon), built and smoke-tested next to the Linux one; CI runs the full test suite
and the corpus gate on macos-26. -
Strict mode infers the signature of an unannotated public function or method that
only the module itself calls; conflicting call sites,--extmodules and exported
functions still need annotations (T0001). -
--non-strictcompiles more everyday code natively: CPython objects as indexes,
slice bounds andrange()arguments (__index__), mixed-type containers promoted to
CPython containers,str % args,strmethods andsys.stdout.writewith object
arguments, keyedsort/sorted/min/maxandheapq/bisecton objects,
x is True/x is False, names bound on only some paths (unbound reads raise
UnboundLocalError/NameErroras in CPython),open(0)as standard input,
sys.stdin.bufferas bytes. -
10 ** -10is a float, as in CPython (was a compile error). -
The generator protocol, natively in strict mode:
send(),throw(),
close()(GeneratorExit, "generator ignored GeneratorExit"),yieldas
an expression,return valueasStopIteration.value,yield from
delegating all three and returning the subgenerator's value, PEP 479, and
generators in module globals closed bydeland at exit. -
Full call signatures:
*args, keyword-only parameters,**kwargs,
positional-only parameters and defaults of every kind, for functions,
methods, constructors and nested functions, called with keywords,*seq
and**mapping, with CPython'sTypeErrormessages. -
Function values (module-level functions and closures) stored in lists,
dicts and instance attributes and called from there (fs[0](x),
table[k](x),self.f(x));print,str()and f-strings render a
function or a generator as CPython does (<function f at 0x...>,
<generator object g at 0x...>). -
--ext: a public function or method with defaults, positional-only or
keyword-only parameters,*argsor**kwargsis published to Python
callers with its declared signature (#1194); CPython binds the call and
raises its ownTypeErrors.
Fixed
- Debug builds (no
--release) stored aNone-typed variable --x = None,
orgot = yield vin a generator whose send type isNone-- as two bytes
into its one-byte slot, overrunning the stack frame: depending on the
layout a generator lost a yielded value or the program crashed. Codegen
now refuses, as an internal error in both profiles, any store wider than
the stack slot or global it writes. - A nested function's plain assignment to a
nonlocalvariable (k = v, not
onlyk += v) is seen by the enclosing function; it used to rebind a copy. sys.stdin.bufferread asstrprinted and compared unlike CPython's bytes.- Security review: an object used as an index could leak a heap int; a
StopIteration.valueof another type is refused instead of reinterpreted;
gen.send(None)is type-checked; numpy array headers are re-read after any call
that may run Python.
Performance
- Every benchmark is within 3x of its Rust twin (fib 1.8x, mandelbrot 1.1x, nbody 2.4x,
sieve 2.1x, spectral_norm 1.8x; v0.1: up to 4.2x). Whole-program proofs drop checks that
cannot fire: an attribute every constructor assigns before the instance escapes skips the
"assigned" test; a function that cannot raise is called without the exception check; a
defthat precedes all module code skips the call-before-definition check. A tree of
intoperators tests its leaves' tags once;x += yreleases the old value only on the
bigint path; range loops with one level of nested loops count with a machine integer;
x ** 0.5provespow == sqrtwith exact integer arithmetic. bench/run.py: best of 7 by default; Rust twins build for the default target CPU and
black-box every input.PYCC_DUMP_LLVM=<path>writes the LLVM IR before and after optimization.
Known gaps
- See
docs/DEVIATIONS.mdfor every known difference from CPython 3.13.
pycc-next v0.1.0
First release of pycc-next: an ahead-of-time compiler for typed Python that turns
type mismatches into compile errors and runs 11-34x faster than CPython on the
benchmark set, interoperating with CPython through extension modules and an
embedded interpreter. Everything below in 0.1.0-rc.1 and 0.1.0-rc.2 is included.
Highlights:
- Strict mode (default): Rust-style diagnostics at the offending code; native
representations; generators, closures, classes, exceptions, containers, bigints. --non-strict: untyped code compiles; unprovable parts run through CPython,
per statement if needed. 200/200 real-world corpus programs match CPython.--ext: abi3 CPython extension modules (CPython >= 3.13).- Reference-counted memory with no per-iteration leaks; security review fixes
(keyed str hashing, int(str) digit limit, no host aborts on big ints). - Benchmarks vs CPython 3.13 / hand-written Rust: fib 10.8x / 3.8x slower,
mandelbrot 33.7x / 1.1x, nbody 23.3x / 4.2x, sieve 16.6x / 3.1x,
spectral_norm 29.2x / 3.5x (see README). - Self-contained Linux x86_64 release archive (needs
cc; CPython 3.13+ with headers
for--extand--non-strict).
Known differences from CPython: docs/DEVIATIONS.md.
pycc-next v0.1.0-rc.2
The release archive is now self-contained (rc.1 could not build programs). Changes
since rc.1:
Added
globalstatements: a module global a function rebinds lives in a one-element
cell, so every function and the module see its writes (native in strict mode).defs inside module-level blocks (for _ in range(t): def solve(): ...) compile
natively when used only after their definition in that block.- Comprehensions with several
for/ifclauses, and starred call arguments
(f(*xs),f(a, *t)) with CPython'sTypeErrorfor a wrong count. - Instance attributes initialised in
__init__from any expression
(self.t = [0] * (n + 1)): the slot takes the value's inferred type. len()of a tuple bound to a name.- Generators, native in strict mode:
defs withyield(annotatedIterator[T],
Iterable[T]orGenerator[T, None, None], or inferred for a private helper),
yield fromstatements,returnin a generator,for/next()/list()/sum()/...
over them, and generator expressions, lazy (soany()/all()stop at the first
deciding element). An unfinished generator is closed when its last reference goes,
running itsfinallyclauses. An--extmodule's public generator returns a
CPython iterator. - Function values (
Callable[[A, B], R]): module-level functions used as values and
nested functions returned or passed as closures (capturing their enclosing variables,
nonlocalcounters included), called through a variable or a call's result
(make_adder(1)(2)). A closure binds a variable when it is made, so one that reads
a variable the enclosing function may still change stays unsupported.
Fixed
- A heap
int(beyond 63 bits) held by a function's parameters or locals is released
when the function returns, and one passed to a builtin (str(x * 2),
max(x, x + 1)) after the call: big-int loops through functions run in constant
memory (#625). - Function values: a call keeps the called closure alive while its arguments run
(an argument rebinding the variable that held it no longer frees it under the
call); a closure capturing a variable rebound byexcept ... as, amatch
capture, atypealias or a comprehension walrus, or used before itsdef, is
refused (strict) or runs in CPython (non-strict) instead of seeing a stale or
unassigned value; the compiler's internal names (__pycc_closure__,_pycc_*)
are reserved (E0109). A--non-strictfallback function annotated with a
compiledtypingimport (Callable) no longer fails withNameError. - The release archive is self-contained and relocatable (#5): it ships
bin/pycc+lib/pycc/libpycc_rt.a, andpyccfinds its runtime relative to
its own executable instead of the checkout it was built in (v0.1.0-rc.1failed
with "no pycc_rt build found (expected /home/runner/work/...)"). The release
workflow smoke-tests the unpacked tarball (native,--ext,--non-strict)
before publishing. A missing C toolchain is reported with what to install. pycc versionreports the LLVM it was built against (fromllvm-config)
instead of a hard-coded22.1.1.
Changed
- Type-checker diagnostics (
T0xxx, and theC0001/I0404the checker raises) point
at the offending expression or statement instead of1:1, with a short caret label
(expectedint, foundstr``) instead of the headline repeated;--error-format jsoncarries the same line, column and length (#10). --non-strictnative builds infer unannotated function signatures from their call
sites, keep imports andsysstream aliases compiled when fallback code reads
them, call CPython's builtin for a builtin call they do not compile, and keep
scalar globals that fallback code only reads native: 83% of the corpus's top-level
statements now compile natively (was 51%) (#11).--non-strict: fallback code calling a compiled function with a parameter default
left out raisedTypeError; such a function now runs in CPython.