← board

What is already done

[[bug-n-abs-is-not-a-value-while-len-and-str-are]] censused twelve names and fixed seven of them. The mechanism is PyBuiltinValueHelper, a Python-name -> pylib-helper table consulted by PyBuiltinValueNameAhead from BOTH doors into a callable value (assignment, and the factor chain for an argument). Adding a name to that table is a one-line change when the name has exactly one helper with a wrappable signature. These five do not.

divmod -- one reason, and it is NOT the mechanical one it looks like

function pydivmod_v(const a: Variant; const b: Variant): TPyList;

Arity 2 is fine: PyGetOrMakeCallableWrapper is arity-generic. The obstacle is the RESULT. A tyClass return is only admitted when PyProcIsFreshContainerCtor vouches that the callee returns a NEWLY constructed container -- otherwise the wrapper would hand back a second owner of an aliased object (the ARC gap bug-nilpy-bound-fn-closure-objects-are-never-freed tracks). That predicate keys on the PYTHON name (list, tuple, dict, bytes, bytearray, reversed, and sorted in pyeval) and the helper here is called pydivmod_v, so it answers False and the wrapper is declined.

AND IT IS RIGHT TO, WHICH I ONLY FOUND BY READING THE BODY. My first draft of this ticket said pydivmod_v is obviously a fresh container ctor and that adding it to the predicate was a one-line change. That is FALSE, and PyProcIsFreshContainerCtor's own comment is why it insists the bodies be read rather than inferred from the Python semantics. pydivmod_v has two exits (pylib.pas around 9582-9621):

numeric path      Result := TPyList.Create; ... append; append      FRESH
user-class path   if PyUserObjDivmod(oa, ob, a, b, r) then
                    pydivmod_v := TPyList(r)                        NOT FRESH

The second returns whatever the program's own __divmod__ handed back, which may be a list the user still holds -- the exact aliasing case the predicate exists to keep out. So divmod is NOT admissible as written, and vouching for it would reintroduce the double-owner bug on any class defining __divmod__.

What it actually needs is one of: a wrapper that copies the result; a split so the dunder path returns a fresh list; or a value-side entry point that is fresh on both exits. Cheap, but not the one-liner it looks like -- and the shape of the mistake (a routine that is fresh on the path you read first and aliasing on the one you did not) is the general hazard for anything else added to that predicate.

enumerate / filter / map / zip -- a real design question

These are not one function each:

enumerate   pyenumerate  pyenumerate2
filter      pyfilter_call  pyfilter_iter  pyfilter_iter_i
map         pymap_call  pymap_iter  pymap_iter_i  pymap_int  pymap_str  pymap_float
zip         pyzip

The parser picks among them from the ARGUMENT's static type -- that is what pymap_iter_i versus pymap_iter is, and pymap_int/pymap_str/pymap_float are the conversion forms. A bare map taken as a value has no argument yet, so there is nothing to pick with. Binding one arm would produce a callable that is silently wrong for every other argument shape, which is exactly the defect [[bug-n-min-and-max-as-a-value-bind-to-the-two-argument-arm-in-the-wrong-unit]] describes -- do not repeat it here to close a row.

What these actually need is a single Variant-in entry point per name that dispatches on the run-time tag, the way pyabs_v already does for abs. That is a pylib addition, not a frontend one, and it is the honest shape of this ticket. zip may be the cheap one -- it has a single entry already.

Ranked low on purpose

map(abs, xs) -- the idiom that started the group -- works now. map ITSELF as a value (f = map) is rare in real Python, and filter/enumerate/zip as bare values are rarer still; they are almost always called immediately. divmod is the most likely of the five to appear in real code, and is cheap but NOT mechanical -- see the two-exit note above. Rank by that rather than by the count of names.

Positive control for whoever takes it

Any fixture here must assert the value form against MORE THAN ONE argument shape, because the failure mode of a wrong pick is a callable that works for the shape you tested. map(str, xs) versus map(len, xs) is the cheap pair: they take different pylib arms today.