Compare object keys by UTF-16 code unit in the rules engine - #7562
Draft
ajpallares wants to merge 8 commits into
Draft
Compare object keys by UTF-16 code unit in the rules engine#7562ajpallares wants to merge 8 commits into
ajpallares wants to merge 8 commits into
Conversation
A JS string is a sequence of UTF-16 code units and every string operation works on those units. Swift's String compares canonical equivalence over Unicode scalars, so <, <=, >, >=, ==, ===, in and rc.entries key ordering all had cases where a predicate evaluated one way on iOS and the other in JS and Kotlin. Co-authored-by: Cursor <cursoragent@cursor.com>
The fixture files are byte-identical across the iOS and Android engines, so a description that explains the contract in terms of one host language's string comparison does not read correctly in the other repository. Co-authored-by: Cursor <cursoragent@cursor.com>
The predicates spell these strings as \uXXXX escapes so the exact code units are unambiguous, which leaves a reader unable to tell which character a fixture is about. Each description now shows the glyph and its code point, and spells out the surrogate pair where one is the point of the case. Co-authored-by: Cursor <cursoragent@cursor.com>
…-utf16-string-comparison Co-authored-by: Cursor <cursoragent@cursor.com> # Conflicts: # Tests/UnitTests/RulesEngine/PredicateFixtureTests.swift
The operator open-coded the UTF-16 comparison because the shared helper did not exist yet when it landed. It does now, and the rationale lives in the helper's doc, so the operator no longer repeats it. Co-authored-by: Cursor <cursoragent@cursor.com>
…-utf16-string-comparison Co-authored-by: Cursor <cursoragent@cursor.com> # Conflicts: # Tests/UnitTests/RulesEngine/PredicateFixtureTests.swift
A Swift dictionary keys by canonical equivalence, so é (U+00E9) and e followed by a combining acute (U+0301) were one key: an object holding both spellings kept only one, and a var path spelled either way resolved against the other. JS and Kotlin keep the two apart, so a rule reading a non-ASCII key could answer differently per platform. Value.object now stores an ObjectValue, whose keys compare and hash by code unit, and the JSON parser iterates the NSDictionary rather than casting to [String: Any], which merges the keys before the engine sees them. Co-authored-by: Cursor <cursoragent@cursor.com>
This was referenced Sep 1, 2026
Base automatically changed from
pallares/rules-engine-utf16-string-comparison
to
main
September 1, 2026 08:51
…-object-key-identity The merge is semantic in three places: - rc.length was removed from main (#7568), so the two rc_entries fixtures this branch adds count their entries with reduce instead. The branch's edit to a description in rc_length.json goes with the deleted file. - rc.let (#7506) landed after this branch was written and keeps its bindings in a Swift dictionary, which is the same key collapse this branch is about: two spellings of the same glyph bound in one rc.let became one binding, and a var path spelled either way resolved against the other. Scope.bindings is now an ObjectValue, so bindings are keyed by code unit like every other object. - expectedCount recomputed from disk as 681. Co-authored-by: Cursor <cursoragent@cursor.com>
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.
Checklist
purchases-androidand hybridsMotivation
Related to #7518. That PR routed every string comparison through UTF-16 code units, but object keys still went through Swift's
Dictionary, which keys by canonical equivalence. So a rule reading a non-ASCII key could resolve on iOS and fail on Android.Description
varpath spelledcaféwith é as U+00E9 resolved against a key spelling ite+ U+0301, where JS and Kotlin find nothing. Subscriber attribute names are developer-supplied and unrestricted, so both spellings are reachable.purchases-androidneeds the new fixtures only: Pin object key identity in the predicate fixtures purchases-android#4149.