Known Bugs and Limitations
Last updated: 2026-11-13. Every item below was checked against the code, by running a program under p2w and CPython: most on 2026-10-08, the items marked (2026-11-13) on that date.
p2w's rule is to refuse what it cannot compile faithfully: an unsupported construct raises NotImplementedError at compile time, naming the construct, rather than compiling to something that runs and gives a wrong answer. Most items below are refusals. The ones that compile and misbehave are listed first.
Test Suite Status
- 1333 passed, 6 skipped (the 6 are the two out-of-scope programs, in three test classes each).
- 102 of the 104 programs in
programs/internal/ match CPython's output and are asserted in WORKING_PROGRAMS.
Wrong at run time
| Program or construct |
What happens |
gen.throw(E) into a try around the yield (generator_protocol.py) |
The exception escapes as GeneratorExit instead of reaching the handler. send() works. |
gen.close() on a generator suspended in a try |
GeneratorExit is not raised inside it, so its except GeneratorExit and cleanup do not run. |
A builtin called with keywords through another name (f = sorted; f(xs, reverse=True)) |
TypeError at run time; CPython accepts it. |
'é'.upper(), 'ÉCOLE'.lower() |
Case mapping is ASCII only: other characters are left as they are. |
Integers past 64 bits (2 ** 64, a large factorial) |
OverflowError. Ints are i31 or i64; there are no big ints. |
Two reads of a method, a.m == a.m |
False: each read makes a new bound method, compared by identity. |
type(obj) for an instance of a class with __slots__ (2026-11-13) |
type(obj) is C is False, and type(obj).__name__ traps (illegal cast). isinstance works. |
obj.__class__ (2026-11-13) |
AttributeError. type(obj) works for classes without __slots__. |
super() in a method whose first parameter is not named self (2026-11-13) |
AttributeError. |
bytes.split (2026-11-13) |
Traps (illegal cast). |
dict.popitem, dict.fromkeys, float.is_integer, bytes.decode (2026-11-13) |
AttributeError at run time. |
The truth value of an instance (if obj:, bool(obj)) (2026-11-13) |
Always true: __bool__ and __len__ are not called. |
| Implicit exception chaining (2026-11-13) |
__context__ is None. raise ... from sets __cause__. |
1 + "a", "a" + 1 (2026-11-13) |
Traps (illegal cast) instead of raising TypeError. |
__and__, __or__, __xor__ on instances (2026-11-13) |
TypeError: the bitwise operators do not dispatch to them. The arithmetic operators do. |
Slicing or iterating over bytes (2026-11-13) |
TypeError. Indexing and len work. |
ascii("é") (2026-11-13) |
Non-ASCII characters are not escaped. |
Refused at compile time
Language
| Construct |
Notes |
async / await |
Needs WASM stack switching, not standardized. async.py is out of scope. |
A yield in a finally, or in a handler or else beside one |
|
A yield inside and/or, a conditional expression, or a later comparison of a chain |
A yield elsewhere in an expression is hoisted into a statement. |
Positional-only parameters (def f(a, /, b)) |
|
| A function or lambda defined in a comprehension that uses the comprehension's variable |
It would read a stale value. |
Assignment or deletion of a slice with a step (xs[::2] = ..., del xs[::3]) |
|
Augmented assignment to a slice (xs[1:3] += [9]) |
Items, names and attributes work, in place where the type does that. |
Mapping patterns capturing the rest (case {"a": x, **rest}) |
|
Class patterns, of any class (case Point(x=0), case int(n)) (2026-11-13) |
Value, singleton, capture, wildcard, or-, sequence and mapping patterns work, with guards. |
| More than one base class (2026-11-13) |
Single inheritance works. |
| Class decorators (2026-11-13) |
Function and method decorators work. |
Format specs with replacement fields (f"{x:{width}}") |
|
Format specs with an explicit sign (+, space), #, or the e, E, g, G types (2026-11-13) |
Fill, alignment, zero padding, width, ,, precision and d b o x f % work. |
str.format with ** arguments, or a field like {0.x} or {0[1]} |
Constant templates are compiled to f-strings; other templates use a runtime helper that takes positional arguments only. |
Metaclasses, __del__ |
Not planned; WASM GC has no finalizers. |
Builtins and methods
| Missing |
Notes |
Builtin methods as values (key=d.get, it.__next__, map(str.upper, xs)) |
Methods of the program's classes work as values. |
bytearray, frozenset, vars |
Not defined. |
str.expandtabs, float.as_integer_ratio |
|
Iteration through __getitem__ (the old sequence protocol) |
__iter__/__next__ work. |
print(..., file=...) |
sys is not importable. |
Keywords to a builtin that ignores them (sum(xs, start=10)) |
Refused rather than dropped. |
Modules
Only math (compiled to WebAssembly, with a host import for the transcendental functions) and typing (compiled away: TYPE_CHECKING is False, cast(T, x) is x, any other name read at run time is refused) can be imported, plus js for browser interop. collections, itertools, functools, dataclasses, enum, abc, sys, os, copy, heapq, bisect, json, re, random, string, operator and time are refused.
JS interop incomplete edges
$js_get_property returns a STRING or a handle, but does not always detect which (wat/helpers/js_interop.py:333).
- The generic
$js_call_method does not pass its arguments to JS yet (wat/helpers/js_interop.py:385).
| Area |
Issue |
| Untyped code |
Without type annotations, values stay boxed: roughly CPython's speed, not faster. With them, the benchmark suite runs at 9.13x CPython (2026-10-08). |
| Nested lists of numbers (matmul, nbody) |
Each element is a boxed reference; unboxed numeric lists are planned. |
Attributes of classes without __slots__ |
A linked list of name/value pairs, scanned on each access. Slotted classes compile to structs. |
| Calls |
Every function checks on entry for keywords from a call that did not know its signature (one ref.test). |
Programs Out of Scope
| Program |
Blocker |
async.py |
async/await |
generator_protocol.py |
throw() into a try (above); listed in OUT_OF_SCOPE; a fix is planned. |