Unicode 18 UCD tables - #6448
Charlie Lin (clin1234) wants to merge 11 commits into
Conversation
Regenerate from newer Unicode 18.0.0 revisions of the UCD files: DerivedGeneralCategory, EastAsianWidth and GraphemeBreakProperty (2026-06-29), DerivedCoreProperties (2026-08-07), and GraphemeBreakTest (2026-06-12). This is not a Unicode version bump; everything stays 18.0.0. The only content change is that the Chisoi block (U+16D80..U+16DA9) has been unassigned again, which shrinks four tables: _Grapheme_Break_property_data 1394 -> 1392 _Indic_Conjunct_Break_property_data 485 -> 483 __printable_property_data 743 -> 741 _Grapheme_Extend_property_data 391 -> 389 _Extended_Pictographic_property_data and _Width_estimate_intervals_v2 are unchanged (the Chisoi code points were East_Asian_Width=N). The changes to GraphemeBreakTest.txt are cosmetic - the rule numbers in the trailing comments were renumbered - so the 853 test cases in the clusterization test are identical and only the date comment moves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Azure Pipelines: Successfully started running 1 pipeline(s). There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
🟡 Changes recommended
The updated grapheme expectations conflict with the unchanged GB9c implementation.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Updates Unicode-dependent formatting tables and grapheme-cluster test data to Unicode 18.0.0.
Changes:
- Updates Unicode source references.
- Regenerates formatting UCD tables.
- Regenerates grapheme-break test vectors.
File summaries
| File | Description |
|---|---|
tools/unicode_properties_parse/unicode_properties_data_gen.py |
Updates Unicode 18 UCD sources. |
tools/unicode_properties_parse/grapheme_break_test_data_gen.py |
Updates the Unicode 18 grapheme-test source. |
tests/std/tests/P0645R10_text_formatting_grapheme_clusterization/test.cpp |
Adds regenerated Unicode 18 grapheme data; GB9c expectations require implementation updates, and generated markers should be preserved. |
stl/inc/__msvc_format_ucd_tables.hpp |
Contains regenerated Unicode 18 formatting tables. |
Review details
Suppressed comments (1)
tests/std/tests/P0645R10_text_formatting_grapheme_clusterization/test.cpp:16
- This regeneration removes the
// Generator:and// Beginning of generated datamarkers from the generated section, while the matching// End of generated datamarker remains at line 1175. Please preserve the paired delimiters (and updatecpp_templateif these are meant to be regenerated) so the checked-in file does not advertise an unmatched generated region.
// GraphemeBreakTest-18.0.0.txt
// Date: 2026-06-12, 00:44:16 GMT
- Files reviewed: 4/4 changed files
- Comments generated: 1
- Review effort level: Lite (auto)
Note
Copilot is running an experiment and ran this review at Lite.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| {{U'\x915', U'\x93c', U'\x94d', U'\x200d', U'\x924'}, {0, 5}}, | ||
| {{U'\x915', U'\x94d', U'\x924', U'\x94d', U'\x92f'}, {0, 5}}, {{U'\x915', U'\x94d', U'\x61'}, {0, 2, 3}}, | ||
| {{U'\x61', U'\x94d', U'\x924'}, {0, 2, 3}}, {{U'\x3f', U'\x94d', U'\x924'}, {0, 2, 3}}, | ||
| {{U'\x61', U'\x94d', U'\x924'}, {0, 3}}, {{U'\x3f', U'\x94d', U'\x924'}, {0, 3}}, |
There was a problem hiding this comment.
🔵 Needs a closer look
The Unicode 18 corpus exposes unsupported GB9c behavior, and the test regeneration loses provenance markers and adds concatenated status output.
Review details
Suppressed comments (3)
Previously missed (1) — in code that hasn't changed since the last review.
tests/std/tests/P0645R10_text_formatting_grapheme_clusterization/test.cpp:17
- The regenerated corpus drops the existing generator and “Beginning of generated data” markers while retaining the closing marker at line 1176. That removes the provenance and regeneration warning for this generated block; please emit these markers from the generator and regenerate the file so future updates preserve them.
tests/std/tests/P0645R10_text_formatting_grapheme_clusterization/test.cpp:436
- These expected breaks changed from
{0, 2, 3}to{0, 3}, but the current_GB9c_regexcannot produce that result: U+0061/U+003F haveInCB=No_value, so after U+094D (Linker) the regex resets instead of matching GB9c. The iterator therefore returns a break at index 2 andrun_unicode_test_data_utf32()asserts; please update the grapheme algorithm for the Unicode 18 rule (and its tests), or defer this corpus update until the implementation supports it.
{{U'\x61', U'\x94d', U'\x924'}, {0, 3}}, {{U'\x3f', U'\x94d', U'\x924'}, {0, 3}},
tests/std/tests/P0645R10_text_formatting_grapheme_clusterization/test.cpp:1359
- These new success messages have no
\n, so a passing run emits all six statuses concatenated on one line before the test runner's output. This is the onlyPassed ...output in the standard test suite; please remove this debugging output or terminate each message with a newline if it is intentional.
std::printf("Passed run_unicode_test_data_utf32()");
run_unicode_test_data_utf32_utf8_comparison();
std::printf("Passed run_unicode_test_data_utf32_utf8_comparison()");
- Files reviewed: 4/4 changed files
- Comments generated: 0 new
- Review effort level: Lite (auto)
Note
Copilot is running an experiment and ran this review at Lite.
This reverts commit 23ebfd7.
There was a problem hiding this comment.
🟡 Changes recommended
The GB9c matcher has a correctness issue and grapheme iteration performs a redundant property lookup.
Get a fresh assessment by requesting another Copilot review.
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 2
- Review effort level: Lite (auto)
Note
Copilot is running an experiment and ran this review at Lite.
| return *this; // GB2 Any % eot | ||
| } | ||
| _Right_gbp = _Grapheme_Break_property_data._Get_property_for_codepoint(*_WrappedIter); | ||
| _Right_InCB = _Indic_Conjunct_Break_property_data._Get_property_for_codepoint(*_WrappedIter); |
There was a problem hiding this comment.
🟡 Changes recommended
Fix the two GB9c matching issues and remove the unconditional diagnostic output.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (2)
stl/inc/format:374
_Matchis called with the current left code point before the right-hand property is tested, soLinker × Consonantmust be accepted when this transition is taken. Returningfalsehere makes every direct GB9c pair (for example the generated0x094D × 0x0915case attest.cpp:102) break instead of matching the Unicode 18 rule.
if (_Left_InCB == _Indic_Conjunct_Break_property_values::_Linker_value) {
_State = _Linker;
}
return false;
stl/inc/format:380
- The accepting suffix is
Linker Extend*: anExtendhere must keep the match true, while any other property must reset the DFA rather than return true and leave_Stateas_Linker. As written,Linker Extend* × Consonantfails, and a later unrelated character can be treated as a GB9c prefix.
if (_Left_InCB == _Indic_Conjunct_Break_property_values::_Extend_value) {
return false;
}
return true;
- Files reviewed: 5/5 changed files
- Comments generated: 1
- Review effort level: Lite (auto)
Note
Copilot is running an experiment and ran this review at Lite.
86cfb64 to
20d8e3f
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
Two moderate issues remain, along with one generated-data marker nit.
Review details
Suppressed comments (3)
stl/inc/format:374
- The transition into
_Linkeris the accepting state shown in the DFA, but this branch still returnsfalse. Because_Matchis called with the current left code point before_Right_InCBis tested, aLinker x Consonantboundary never matches GB9c; the newU+1CF5 U+0995case attest.cpp:339-340therefore fails. Returntruewhen consuming the linker.
if (_Left_InCB == _Indic_Conjunct_Break_property_values::_Linker_value) {
_State = _Linker;
}
return false;
tests/std/tests/P0645R10_text_formatting_grapheme_clusterization/test.cpp:1189
- This unconditional diagnostic prints one line for every emitted grapheme cluster on every successful run, flooding CI logs and adding needless formatting overhead to this regression test. Please remove the debug
printf(and the now-unused<cstdio>include), or make the output failure-only.
printf("Iter: %lX, rhs: %lX\n",
(unsigned long)*iter,
(unsigned long)test_case.code_points[test_case.breaks[i]]
);
tests/std/tests/P0645R10_text_formatting_grapheme_clusterization/test.cpp:17
- The generated block's opening provenance markers were removed while the closing
// End of generated datamarker remains. This leaves the generated-data delimiters unbalanced and removes the direct script reference that helps future UCD refreshes; please retain these markers (or have the generator emit them).
// GraphemeBreakTest-18.0.0.txt
// Date: 2026-06-12, 00:44:16 GMT
- Files reviewed: 5/5 changed files
- Comments generated: 0 new
- Review effort level: Lite (auto)
Note
Copilot is running an experiment and ran this review at Lite.
20d8e3f to
b0d1a50
Compare
e43d4f0 to
d9362df
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved grapheme-clustering correctness issues and a test compilation error block approval.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (3)
stl/inc/format:374
- Because
_Matchis invoked on_Left_InCBbefore evaluating the current pair (line 477), the transition that consumes aLinkermust be accepting. Returningfalsehere makesLinker × Consonanta break; the newU+1CF5 U+0995conformance case attest.cpp:449expects{0,2}and will fail. Return the accepting result when entering_Linker.
if (_Left_InCB == _Indic_Conjunct_Break_property_values::_Linker_value) {
_State = _Linker;
}
return false;
stl/inc/format:380
- In
_Linker,Extendis part of the acceptedLinker Extend*prefix, but this branch returns false for it; every other value returns true and leaves_Stateas_Linker. That both missesLinker Extend* × Consonantand lets a prior linker cause unrelated... × Consonantpairs to join across a break. Keep the accepting state forExtend(and a newLinker), but reset and return false for other properties.
case _Linker:
if (_Left_InCB == _Indic_Conjunct_Break_property_values::_Extend_value) {
return false;
}
return true;
stl/inc/format:473
- Adding
_Right_InCBhere introduces a second property-table lookup for each code point: the loop's increment expression still initializes_Left_InCBby looking up the same*_WrappedIter, while this line already computed that value as_Right_InCB. Reuse_Right_InCBwhen advancing the loop, as is already done for_Right_gbpand_Right_ExtPic, to avoid an extra binary search through the 483-range table per code point.
_Right_InCB = _Indic_Conjunct_Break_property_data._Get_property_for_codepoint(*_WrappedIter);
- Files reviewed: 5/5 changed files
- Comments generated: 1
- Review effort level: Lite (auto)
Note
Copilot is running an experiment and ran this review at Lite.
d9362df to
40e87e6
Compare
There was a problem hiding this comment.
🟡 Changes recommended
The GB9c transition and test diagnostic issues remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (1)
stl/inc/format:374
_GB9c_Matchis evaluated for the boundary after the current code point, so aLinkeritself is an accepting suffix ofLinker Extend*(zeroExtends are allowed). Returningfalsehere makes the new rule fail for the simplestInCB=Linkerfollowed byInCB=Consonantcase, including the generated0x094D, 0x0915case attests/std/tests/P0645R10_text_formatting_grapheme_clusterization/test.cpp:102. Returntruewhen transitioning to_Linker.
return false;
- Files reviewed: 5/5 changed files
- Comments generated: 1
- Review effort level: Lite (auto)
Note
Copilot is running an experiment and ran this review at Lite.
There was a problem hiding this comment.
🔵 Needs a closer look
GB9c state handling can incorrectly suppress a required grapheme break.
Review details
Suppressed comments (1)
stl/inc/format:380
- This branch treats every non-
Extendcode point as a successful continuation and never leaves_Linker. That lets aLinkerfollowed by anInCB=Nocharacter (for exampleU+1CF5 × U+200C ÷ U+0995, covered by the generated case attest.cpp:450) incorrectly suppress the later break. Accept onlyExtend(or a newLinkerstart), otherwise reset to_Startand returnfalse.
if (_Left_InCB == _Indic_Conjunct_Break_property_values::_Extend_value) {
return false;
}
return true;
- Files reviewed: 5/5 changed files
- Comments generated: 0 new
- Review effort level: Lite (auto)
Note
Copilot is running an experiment and ran this review at Lite.
5c90832 to
67ab95a
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
GB9c state handling is incorrect, and the test does not verify all expected break positions.
Review details
Suppressed comments (3)
stl/inc/format:374
_Matchreturnsfalsewhen it first sees aLinker, and also for everyExtendafter it, so_GB9c_Matchcan never be true for the newLinker Extend* × Consonantrule. For example, the regeneratedU'\x94d', U'\x915'case will still break at the linker instead of producing the expected single cluster; the non-Extendpath also needs to reset the DFA so it cannot carry a stale match across unrelated characters.
if (_Left_InCB == _Indic_Conjunct_Break_property_values::_Linker_value) {
_State = _Linker;
}
return false;
stl/inc/format:380
- While in
_Linker, this branch returnstruefor every non-Extendproperty and never resets the state. After a Linker followed by anInCB=Nocharacter such as U+200C, the next consonant is therefore incorrectly accepted by GB9c; the new test attest.cpp:450expects a break there. Keep the accepting transition only forExtend/Linkerand reset to_Startotherwise.
case _Linker:
if (_Left_InCB == _Indic_Conjunct_Break_property_values::_Extend_value) {
return false;
}
return true;
tests/std/tests/P0645R10_text_formatting_grapheme_clusterization/test.cpp:1190
- This loop only validates the emitted prefix and never checks that all expected break positions were emitted. If the iterator reaches the sentinel early (for example, by missing a required break in the new U+1CF5 U+200C U+0995 case), the test exits successfully without detecting it. Assert that the number of emitted cluster starts is one less than
breaks.size()after the loop.
if (*iter != test_case.code_points[test_case.breaks[i]]) {
fprintf(stderr, "Iter: %lX, rhs: %lX\n", (unsigned long) *iter,
(unsigned long) test_case.code_points[test_case.breaks[i]]);
assert(false);
}
- Files reviewed: 5/5 changed files
- Comments generated: 0 new
- Review effort level: Lite (auto)
Note
Copilot is running an experiment and ran this review at Lite.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
67ab95a to
408702b
Compare
There was a problem hiding this comment.
🟡 Changes recommended
The GB9c state machine has unresolved correctness issues, plus a diagnostic-label nit.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (3)
stl/inc/format:374
- This transition enters the accepting
_Linkerstate but returnsfalse; the_Linkerbranch likewise returnsfalseforExtend. As a result, the newLinker Extend* × Consonantrule is not recognized for sequences such as U+094D U+0924 in the regenerated test data. Make the Linker/Extend transitions accept the current left code point, and reset to_Startwhen a non-Extend/non-Linker value ends the pattern so the state cannot leak into later boundaries.
if (_Left_InCB == _Indic_Conjunct_Break_property_values::_Linker_value) {
_State = _Linker;
}
return false;
stl/inc/format:380
- In
_Linker, the DFA must continue acceptingLinker Extend*and reset when the current property is unrelated. ReturningfalseforExtendshifts the match, while returningtruefor every other property leaves the state stale; for example, the newU+1CF5 U+200C U+0995case attest.cpp:450is incorrectly joined instead of breaking before the consonant. AcceptExtend/Linkerhere and reset to_Startotherwise.
case _Linker:
if (_Left_InCB == _Indic_Conjunct_Break_property_values::_Extend_value) {
return false;
}
return true;
stl/inc/format:380
- For any non-
Extendvalue other than a newLinker, this unconditionaltrueleaves_Stateas_Linkerafter a grapheme boundary. Consequently, afterLinker Consonant, the following ordinaryConsonantcan be treated as GB9c context and incorrectly suppress its break; reset to_Startwhen the current value is notLinker.
return true;
- Files reviewed: 5/5 changed files
- Comments generated: 1
- Review effort level: Lite (auto)
Note
Copilot is running an experiment and ran this review at Lite.
| """ | ||
| Generate test data from "GraphemeBreakTest.txt" | ||
| This file can be downloaded from: https://www.unicode.org/Public/UCD/latest/ucd/auxiliary/GraphemeBreakTest.txt | ||
| This file can be downloaded from: https://www.unicode.org/Public/18.0.0/ucd/auxiliary/GraphemeBreakTest.txt |
There was a problem hiding this comment.
Do we want to refer to a fixed version instead of latest? Ditto below.
There was a problem hiding this comment.
I think it's more preferable to point to the specific version corresponding to the generated files
There was a problem hiding this comment.
🟡 Changes recommended
One or more issues must be addressed before approval.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
tests/std/tests/P0645R10_text_formatting_grapheme_clusterization/test.cpp:16
- The regenerated block now starts directly with the Unicode file metadata, removing the
Generator:andBeginning of generated datasentinels while the matchingEnd of generated datasentinel remains at line 1175. This leaves the generated-data markers inconsistent and loses the documented regeneration source; please restore the two sentinels (or remove the end sentinel too).
stl/inc/format:477
- This unconditional
_Get_property_for_codepointcall adds a binary-search table lookup for every code point, even though_Right_InCBis only consumed when_GB9c_Matchis true below. This iterator is used by the string-width hot path, so defer the lookup or inline it in the guarded GB9c condition to avoid charging ordinary text for the new property lookup.
_Right_InCB = _Indic_Conjunct_Break_property_data._Get_property_for_codepoint(*_WrappedIter);
- Files reviewed: 5/5 changed files
- Comments generated: 1
- Review effort level: Lite (auto)
Note
Copilot is running an experiment and ran this review at Lite.
There was a problem hiding this comment.
🔵 Needs a closer look
Resolve the GB9c documentation/behavior mismatch and restore the generated-data markers.
Review details
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
stl/inc/format:543
- The DFA keeps accepting after another
InCB=Linker, but the adjacent regex documents onlyLinker Extend*. This mismatch is observable for the Unicode 18 test caseU+0915 U+094D U+094D U+0924, which expects no break and relies on the Linker loop. Please document the implemented repetition (or change the DFA if the narrower rule is intended).
tests/std/tests/P0645R10_text_formatting_grapheme_clusterization/test.cpp:16
- The regeneration dropped the existing
Generator:andBeginning of generated datamarkers while leaving theEnd of generated datamarker at line 1175. This removes the documented path for reproducing the large fixture and makes the generated region ambiguous; please retain these markers (or emit them fromcpp_template) when refreshing the data.
// GraphemeBreakTest-18.0.0.txt
// Date: 2026-06-12, 00:44:16 GMT
- Files reviewed: 5/5 changed files
- Comments generated: 0 new
- Review effort level: Lite (auto)
Note
Copilot is running an experiment and ran this review at Lite.
|
Stephan T. Lavavej (@StephanTLavavej) Unicode 18 is officially out: https://blog.unicode.org/2026/09/announcing-unicode-standard-version-180.html |
Regenerate UCD tables for
<format>and Grapheme Cluster breaksGetting closer to 18's final release on the 16th: https://www.unicode.org/versions/beta-18.0.0.html
Claude was only used to discover that the Chisoi block was unassigned; all subsequent effort consisted of manually pulling updated UCD, regenerating relevant files, and manually-inspecting them for correctness.