Skip to content

GH-51225: [C++] utf8_normalize: compose for NFC and NFKC - #51237

Open
singhpratech wants to merge 1 commit into
apache:mainfrom
singhpratech:GH-51225-utf8-normalize-compose
Open

GH-51225: [C++] utf8_normalize: compose for NFC and NFKC#51237
singhpratech wants to merge 1 commit into
apache:mainfrom
singhpratech:GH-51225-utf8-normalize-compose

Conversation

@singhpratech

@singhpratech singhpratech commented Sep 8, 2026

Copy link
Copy Markdown

Rationale for this change

utf8_normalize with form=NFC or NFKC returned the decomposed forms (NFD, NFKD). Utf8NormalizeBase
builds the right utf8proc options for each form but only calls utf8proc_decompose(), which
decomposes regardless of UTF8PROC_COMPOSE; the composition step lives in
utf8proc_normalize_utf32(), which the kernel never called. See #51225.

What changes are included in this PR?

  • After a successful utf8proc_decompose(), when the options include UTF8PROC_COMPOSE, call
    utf8proc_normalize_utf32() on the scratch buffer. It composes in place and returns the new code
    point count; the existing UTF-8 encode loop is unchanged. NFD and NFKD take the same path as before.
  • Fix the json_composed fixture in scalar_string_test.cc: its bytes were the decomposed form
    (61 CC 81), the same string as json_decomposed, so the compose assertions were comparing a value
    with itself and passed with the bug. Thanks to @Santoshkumarpuppala for spotting that on the issue.
  • Add composed/decomposed pairs (U+00E9, and a Hangul syllable with its jamo) to
    test_utf8_normalize in pyarrow; the existing input, U+00B2, is its own NFC.

Are these changes tested?

Yes. With the corrected fixture, TestStringKernels.Utf8Normalize fails on the unpatched kernel and
passes with this change; the pyarrow test covers the composed forms from Python.

Are there any user-facing changes?

Yes: utf8_normalize with NFC and NFKC now returns composed output. Callers that depended on the
previous (decomposed) result for those forms will see different bytes.

utf8proc_decompose() only decomposes; call utf8proc_normalize_utf32() on the
scratch buffer when the form asks for composition. Fix the json_composed test
fixture, whose bytes were the decomposed form, and add composed/decomposed
pairs to the pyarrow test.
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

⚠️ GitHub issue #51225 has been automatically assigned in GitHub to PR creator.

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

⚠️ GitHub issue #51225 has no components, please add labels for components.

singhpratech added a commit to singhpratech/ArrowMetal that referenced this pull request Sep 8, 2026

@raulcd raulcd 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.

This is not a critical fix, can you update the description.

// decomposed: U+0061(LATIN SMALL LETTER A) + U+0301(COMBINING ACUTE ACCENT)
// composed: U+00E1(LATIN SMALL LETTER A WITH ACUTE)
const char* json_composed = "[\"foo\", \"\"]";
const char* json_composed = "[\"foo\", \"\xc3\xa1\"]";

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.

this does not seem correct and does not match the comment above

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The literal on main renders as á, but its bytes are 61 cc 81,
a followed by U+0301, the decomposed form, so it was the same string as json_decomposed on the
next line and the compose assertions below compared a value with itself. That is why the test passed
with the bug (Santoshkumarpuppala spotted it on the issue). To check:

git show main:cpp/src/arrow/compute/kernels/scalar_string_test.cc | sed -n 1248p | xxd
...  5c22 61cc 815c 22 ...

\xc3\xa1 is U+00E1 in UTF-8, which is what the comment describes. I used the escape so the two
fixtures are visibly different in the source, the way json_decomposed already is; happy to write the
literal á (composed, c3 a1) instead, or add the byte values to the comment, whichever you prefer.

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

⚠️ GitHub issue #51225 has no components, please add labels for components.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants