Description
When aiter() is called with an object that lacks __aiter__, GraalPy raises TypeError with the message 'async for' requires object with __aiter__ method, got NoneType. CPython raises the same exception type with a different, builtin-specific message: 'NoneType' object is not an async iterable. This is a CPython compatibility issue.
Root Cause
The aiter() builtin in BuiltinFunctions.AIter delegates to GetAIterNode, which is the node used by the async for statement. GetAIterNode raises the async for-specific message ASYNC_FOR_NO_AITER ('async for' requires object with __aiter__ method, got %N), so the builtin leaks an async for-themed message to users who never wrote async for.
CPython keeps the two paths separate: async for goes through the GET_AITER opcode while builtin_aiter calls PyObject_GetAIter, which produces a distinct, object-centric message.
CPython reference: Objects/abstract.c#L2801-L2817
// PyObject_GetAIter raises a builtin-specific message (distinct from the
// GET_AITER opcode used by `async for`) when am_aiter is not implemented.
PyObject *
PyObject_GetAIter(PyObject *o) {
PyTypeObject *t = Py_TYPE(o);
if (t->tp_as_async == NULL || t->tp_as_async->am_aiter == NULL) {
return type_error("'%.200s' object is not an async iterable", o);
}
...
}
Note that GraalPy's own C-API port at com.oracle.graal.python.cext/src/abstract.c already uses the CPython message — only the Java-side builtin entry point reuses the async for node and inherits its message.
Fix
Make AIter replicate PyObject_GetAIter inline (check am_aiter slot, call it, verify the result with PyAIterCheckNode) and add a dedicated NOT_AN_ASYNC_ITERABLE error message, instead of delegating to GetAIterNode.
Reproduction
Output
GraalPy:
TypeError: 'async for' requires object with __aiter__ method, got NoneType
CPython:
TypeError: 'NoneType' object is not an async iterable
Environment
- GraalPy 25.0.2 (Python 3.12.8)
- CPython 3.12.13
- OS: Debian 12
Description
When
aiter()is called with an object that lacks__aiter__, GraalPy raisesTypeErrorwith the message'async for' requires object with __aiter__ method, got NoneType. CPython raises the same exception type with a different, builtin-specific message:'NoneType' object is not an async iterable. This is a CPython compatibility issue.Root Cause
The
aiter()builtin inBuiltinFunctions.AIterdelegates toGetAIterNode, which is the node used by theasync forstatement.GetAIterNoderaises theasync for-specific messageASYNC_FOR_NO_AITER('async for' requires object with __aiter__ method, got %N), so the builtin leaks anasync for-themed message to users who never wroteasync for.CPython keeps the two paths separate:
async forgoes through theGET_AITERopcode whilebuiltin_aitercallsPyObject_GetAIter, which produces a distinct, object-centric message.Note that GraalPy's own C-API port at
com.oracle.graal.python.cext/src/abstract.calready uses the CPython message — only the Java-side builtin entry point reuses theasync fornode and inherits its message.Fix
Make
AIterreplicatePyObject_GetAIterinline (checkam_aiterslot, call it, verify the result withPyAIterCheckNode) and add a dedicatedNOT_AN_ASYNC_ITERABLEerror message, instead of delegating toGetAIterNode.Reproduction
Output
GraalPy:
CPython:
Environment