fix: match by Unicode codepoint so a lone surrogate cannot match half a pair [patch] - #73
Merged
Merged
Conversation
… a pair [patch]
Fuzzy.ContainsCore and Fuzzy.CalculateScoreCore advanced one UTF-16 code
unit at a time, so an unpaired surrogate in the pattern matched the
corresponding half of an unrelated supplementary-plane character in the
subject: Fuzzy.Contains("x\U0001F601y", "\uD83D") returned true.
Both loops now advance one Unicode codepoint at a time. CodepointLengthAt
reports whether an index starts a well-formed surrogate pair, and
CodepointsEqual treats codepoints of differing code-unit length as unequal,
which is what stops the spurious half-match. The scoring loop tracks the
best matched codepoint by index and length rather than by char, so a
surrogate pair is scored as one character instead of two.
Behaviour for BMP text is unchanged: every BMP character is a single code
unit, so both loops iterate exactly as before. Supplementary-plane
codepoints are compared exactly, matching the existing behaviour, since
char.ToLowerInvariant has no case mapping to apply to a surrogate.
Fixes #71
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rcu8VsXN5GQsBmuvn6NQ6y
|
This was referenced Sep 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Fixes #71
What was wrong
Fuzzy.ContainsCoreandFuzzy.CalculateScoreCoreadvanced one UTF-16 code unit at a time, with no awareness of surrogate pairs. An unpaired surrogate in the pattern was therefore an ordinary matchable character and could match the corresponding half of an unrelated supplementary-plane character in the subject:The fix
Both loops now advance one Unicode codepoint at a time:
CodepointLengthAtreports whether an index starts a well-formed surrogate pair (2 code units) or not (1). An unpaired surrogate is a codepoint of its own, so it is never treated as half of a neighbouring character.CodepointsEqualcompares the codepoints at two indices, treating codepoints of differing code-unit length as unequal — that length check is what stops the spurious half-match.char, so a surrogate pair is scored as one character instead of two.char.IsHighSurrogate/char.IsLowSurrogateare used rather thanSystem.Text.Rune, which is not available on thenetstandard2.0/netstandard2.1legs.Behaviour notes
char.ToLowerInvariantoperates on single UTF-16 code units and has no case mapping to apply to a surrogate. Case folding for supplementary-plane characters is out of scope here.This issue was previously triaged as a follow-on to #70; that issue was resolved by PR #72 via NFC normalization rather than codepoint enumeration, so this gap remained and is addressed here on its own terms.
Testing
Added a
Surrogate Pair Testsregion toFuzzySearch.Test/FuzzyTests.cscovering the issue's acceptance criteria — an unpaired high surrogate, an unpaired low surrogate, and the scoring overload against a subject containing U+1F601 adjacent to other matchable characters, plus regression guards that a pair still matches itself, that an unpaired surrogate still matches the same unpaired surrogate, that a whole pair does not match an unpaired surrogate in the subject, and that two emoji sharing a high surrogate are not confused.Verified by reverting
FuzzySearch/Fuzzy.cstomainwith the new tests in place: 3 of the new tests fail (Contains_LoneHighSurrogatePattern_DoesNotMatchHalfOfASurrogatePair,Contains_LoneLowSurrogatePattern_DoesNotMatchHalfOfASurrogatePair,Contains_WithScore_LoneHighSurrogatePattern_IsNotReportedAsPresent). With the fix restored, all 42 tests pass, anddotnet build -c Releasesucceeds with 0 warnings across all five target frameworks.🤖 Generated with Claude Code
https://claude.ai/code/session_01Rcu8VsXN5GQsBmuvn6NQ6y
Generated by Claude Code