Skip to content

Extend the monoid guard to built-ins that carry a UDT fallback func - #619

Open
eriknw wants to merge 1 commit into
37-networkx-int32-widen-pinfrom
38-monoid-guard-udt-fallback-builtins
Open

Extend the monoid guard to built-ins that carry a UDT fallback func#619
eriknw wants to merge 1 commit into
37-networkx-int32-widen-pinfrom
38-monoid-guard-udt-fallback-builtins

Conversation

@eriknw

@eriknw eriknw commented Aug 5, 2026

Copy link
Copy Markdown
Member

Stacked on #618. Overnight adversarial review of #617 found a hole in its guard: _is_builtin_binaryop keys on "no Python func and no numba func", but four built-ins (binary.any, first, pair, second) carry a Python orig_func as their UDT fallback. All four sailed past the guard and reproduced exactly what #617 exists to prevent:

from graphblas import binary, monoid
from graphblas.core.operator.monoid import Monoid

binary.any.monoid is monoid.any        # True
try:
    Monoid.register_anonymous(binary.any, 0)   # opaque AttributeError, not the clear TypeError
except AttributeError:
    pass
binary.any.monoid is monoid.any        # False! left pointing at the half-built monoid

binary.any is even named in #617's own rationale as an op whose identity SuiteSparse ignores. This is an incomplete fix in #617 rather than a regression: the four common cases (plus/times/min/max) were already rejected cleanly.

The fix keys the probe on the typed ops as well: any TypedBuiltinBinaryOp among them marks the op built-in. Verified that the two signals agree everywhere else, including under mapnumpy=True (where mapped numpy binary ops hold the built-in's typed ops and are flagged by both tests alike, and the numpy monoids never pass through Monoid._build), and that a UDF's typed ops are never TypedBuiltinBinaryOp, so nothing legitimate is newly rejected. The new test is teeth-checked: it fails with the opaque AttributeError without the fix.

Gates: full pinned suite 1113 passed / 145 skipped; test_op + test_numpyops green under both mapnumpy settings; pre-commit all hooks pass.

@eriknw
eriknw force-pushed the 38-monoid-guard-udt-fallback-builtins branch from ceeeea2 to aaf597e Compare August 5, 2026 17:44
@eriknw
eriknw force-pushed the 38-monoid-guard-udt-fallback-builtins branch from aaf597e to 8e2c61c Compare August 5, 2026 18:03
@eriknw
eriknw force-pushed the 38-monoid-guard-udt-fallback-builtins branch 2 times, most recently from 56e3ec8 to eef9de4 Compare August 6, 2026 07:59
@eriknw
eriknw force-pushed the 38-monoid-guard-udt-fallback-builtins branch from eef9de4 to 3ddf548 Compare August 6, 2026 15:39
@eriknw
eriknw force-pushed the 38-monoid-guard-udt-fallback-builtins branch from 3ddf548 to 95835be Compare August 6, 2026 15:41
@eriknw
eriknw force-pushed the 38-monoid-guard-udt-fallback-builtins branch 2 times, most recently from 37f2180 to e43f3e9 Compare August 6, 2026 20:41
@eriknw
eriknw force-pushed the 38-monoid-guard-udt-fallback-builtins branch from e43f3e9 to 4430a06 Compare August 7, 2026 02:49
The guard added with the monoid-from-built-in rejection keyed on
"constructed with neither a Python func nor a numba func", which is true
for most built-ins but not all of them: binary.any, first, pair, and
second carry a Python orig_func as their UDT fallback while their typed
ops are still TypedBuiltinBinaryOp. All four sailed past the guard and
reproduced exactly what it exists to prevent: an opaque AttributeError
partway through registration, with binary.<name>._monoid left pointing
at the half-built monoid (binary.any.monoid is monoid.any flipped from
True to False after the rejected call). binary.any is even named in the
guard's own rationale as an op whose identity SuiteSparse ignores.

Key the probe on the typed ops as well: any TypedBuiltinBinaryOp among
them marks the op built-in. The two signals agree everywhere else,
including under mapnumpy=True, where the mapped numpy binary ops hold
the built-in's typed ops and no funcs, and are flagged by both tests
alike; the numpy monoids themselves are mapped without passing through
Monoid._build, so their registration is unaffected (verified under both
mapnumpy settings). A UDF's typed ops are never TypedBuiltinBinaryOp,
so nothing legitimate is newly rejected.
@eriknw
eriknw force-pushed the 38-monoid-guard-udt-fallback-builtins branch from 4430a06 to a30c491 Compare August 7, 2026 05:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant