Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions graphblas/core/operator/udt_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,12 +325,27 @@ def _check_udt_pair(op_name, dtype, dtype2, info_x, info_y):
f"binary.{op_name} does not work with ({dtype}, {dtype2}): "
f"cannot mix record and array UDTs in a single element-wise op."
)
if kind_x == "record" and detail_x != detail_y:
raise KeyError(
f"binary.{op_name} does not work with ({dtype}, {dtype2}): "
f"record UDTs must share field names; got {list(detail_x)} vs "
f"{list(detail_y)}."
)
if kind_x == "record":
if detail_x != detail_y:
raise KeyError(
f"binary.{op_name} does not work with ({dtype}, {dtype2}): "
f"record UDTs must share field names; got {list(detail_x)} vs "
f"{list(detail_y)}."
)
# Matching top-level names is not enough: the codegen pairs operands
# leaf by leaf, and a field that is a sub-record on one side and a
# scalar on the other contributes a different number of leaves. Left
# unchecked the pair reaches Numba, whose typing failure arrives as a
# UdfParseError, reporting a compile error for what is really the same
# shape disagreement the checks above report as a KeyError.
leaves_x = [c for _py, c, _d in _iter_record_leaves(dtype.np_type)]
leaves_y = [c for _py, c, _d in _iter_record_leaves(dtype2.np_type)]
if len(leaves_x) != len(leaves_y):
raise KeyError(
f"binary.{op_name} does not work with ({dtype}, {dtype2}): "
f"record UDTs must nest the same way, so that each has the same "
f"number of leaf fields; got {leaves_x} vs {leaves_y}."
)
if kind_x == "array" and detail_x != detail_y:
raise KeyError(
f"binary.{op_name} does not work with ({dtype}, {dtype2}): "
Expand Down
36 changes: 36 additions & 0 deletions graphblas/tests/test_op.py
Original file line number Diff line number Diff line change
Expand Up @@ -2851,6 +2851,42 @@ def test_udt_eq_ne_rejects_incompatible_pairs():
binary.eq(v_uv & v_arr).new()


@pytest.mark.skipif("not supports_udfs")
# SS < 9 has no GrB_NAME setter, so registration falls back to storing the
# numpy repr in the type name and warns when it does not fit in 128 chars.
# _NestDeep's repr is 142; how it serializes is not what the test is about.
@pytest.mark.filterwarnings("ignore:UDT repr is too large")
def test_udt_record_nesting_mismatch_is_a_keyerror():
"""Records sharing field names but not nesting depth are rejected as a KeyError.

``_check_udt_pair`` matched on top-level names only, but the codegen pairs
operands leaf by leaf, and a field that is a sub-record on one side and a
scalar on the other contributes a different number of leaves. Without the
guard the pair reaches Numba, whose typing failure arrives as a
``UdfParseError``: a compile error reported for what is really the same
shape disagreement its sibling checks raise ``KeyError`` for.
"""
flat = dtypes.register_anonymous(
np.dtype([("nst_a", np.float64), ("nst_b", np.float64)], align=True), "_NestFlat"
)
nested = dtypes.register_anonymous(
np.dtype(
[
("nst_a", np.dtype([("nst_n1", np.float64), ("nst_n2", np.float64)])),
("nst_b", np.float64),
],
align=True,
),
"_NestDeep",
)
v = Vector(flat, size=1)
v[0] = (1.0, 2.0)
w = Vector(nested, size=1)
w[0] = ((3.0, 4.0), 5.0)
with pytest.raises(KeyError, match="same number of leaf fields"):
v.ewise_mult(w, binary.plus).new()


@pytest.mark.skipif("not supports_udfs")
@pytest.mark.slow
def test_udt_aggregators():
Expand Down
Loading