Skip to content

BUG: fix StringDType coerce flag in binary ufunc promotion (#31861) - #31862

Merged
ngoldbaum merged 2 commits into
numpy:mainfrom
Ethan0456:bug-31861-stringdtype-coerce-ufunc
Jul 6, 2026
Merged

ngoldbaum merged 2 commits into
numpy:mainfrom
Ethan0456:bug-31861-stringdtype-coerce-ufunc

Conversation

@Ethan0456

Copy link
Copy Markdown
Contributor

PR summary

Fixes #31861.

Binary StringDType ufuncs resolved output coerce with descr1->coerce && descr1->coerce instead of combining both operands.

That made results order-dependent (e.g. np.add with coerce=True then coerce=False kept coerce=True) and allowed non-string assignment via implicit stringification.

This changes the combination to descr1->coerce && descr2->coerce, matching common_instance / np.promote_types, and adds a small regression test for both operand orders.

First time committer introduction

I have been using NumPy for some time now. I hit this while working with StringDType and coerce, and found two places with similar typo bug. I checked and found that one typo in common_instance has been fixed yesterday, so wanted to fix the remaining typo in the binary ufunc path.

AI Disclosure

I used Grok to help locate the bug in binary_resolve_descriptors, draft the regression test, and refine the issue/PR wording. I made and verified the fix with correctness checks, rebuilds and the test suite; final review and PR submission were done by me.

@shipitdev shipitdev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the patch, @Ethan0456!

For a maintainer reviewing this (just laying out the PR simply for an easier merge):
I reviewed this, and it looks like whoever originally coded this just mistyped int out_coerce = descr1->coerce && descr1->coerce; when trying to resolve the output coerce flag. Because it checked descr1 twice and ignored descr2, binary_resolve_descriptors was returning the wrong output descriptor if the two arrays had different coerce flags.

The tests correctly isolate the bug, seem totally fine to me. Overall, this looks like a super easy merge to me! Thank you.

@ngoldbaum ngoldbaum added 00 - Bug 09 - Backport-Candidate PRs tagged should be backported component: numpy.strings String dtypes and functions labels Jul 5, 2026
Comment thread numpy/_core/tests/test_stringdtype.py Outdated
"coerce_a, coerce_b",
[(True, False), (False, True)],
)
def test_add_preserves_coerce_false(coerce_a, coerce_b):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of adding a new test, couldn't you simply add an assert to test_unset_na_coercion:

# this test uses the "add" and "equal" ufunc but all ufuncs that
# accept more than one string argument and produce a string should
# behave this way
# TODO: generalize to more ufuncs
inp = ["hello", "world"]
arr = np.array(inp, dtype=StringDType(na_object=None))
for op_dtype in [None, StringDType(), StringDType(coerce=False),
StringDType(na_object=None)]:
if op_dtype is None:
op = "2"
else:
op = np.array("2", dtype=op_dtype)
res = arr + op
assert_array_equal(res, ["hello2", "world2"])

Something like:

expected_coerce = op_dtype is None or op_dtype.coerce
assert res.dtype == StringDType(na_object=None, coerce=expected_coerce)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I have dropped the new test and extended test_unset_na_coercion; now each branch builds the expected result dtype (including coerce) before asserting it.

@ngoldbaum ngoldbaum left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching this, I agree the fix is correct.

I left one comment about the new test, which I think is duplicative of existing tests.

AIs like to write new tests, it's good to prompt them to prefer expanding existing tests rather than to write new tests.

@Ethan0456

Copy link
Copy Markdown
Contributor Author

@ngoldbaum Addressed your review feedback in the latest commit, thanks!

@ngoldbaum

Copy link
Copy Markdown
Member

Thanks @Ethan0456!

@ngoldbaum
ngoldbaum merged commit 5075498 into numpy:main Jul 6, 2026
87 checks passed
MaanasArora pushed a commit to MaanasArora/numpy that referenced this pull request Jul 7, 2026
@charris charris removed the 09 - Backport-Candidate PRs tagged should be backported label Jul 7, 2026
charris added a commit that referenced this pull request Jul 7, 2026
BUG: fix `StringDType` coerce flag in binary ufunc promotion (#31862)
ngoldbaum pushed a commit to ngoldbaum/numpy that referenced this pull request Aug 4, 2026
ngoldbaum pushed a commit to ngoldbaum/numpy that referenced this pull request Sep 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

00 - Bug component: numpy.strings String dtypes and functions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG: StringDType(coerce=...) is not preserved by binary ufuncs when operands differ

4 participants