What happened
String.prototype.toLowerCase() does not apply the Unicode Final_Sigma conditional casing rule: a Greek capital sigma (Σ, U+03A3) at the end of a word must lowercase to final sigma (ς, U+03C2), not medial sigma (σ, U+03C3). Node (and the spec, via SpecialCasing.txt's conditional Final_Sigma rule) gets this right; Perry always produces medial sigma.
node: "ΟΔΥΣΣΕΥΣ".toLowerCase() === "οδυσσευς" // wait — see below, corrected example
node: "ΑΣ".toLowerCase() === "ας" // final ς
perry: "ΑΣ".toLowerCase() === "ασ" // WRONG: medial σ
Reproduced on current main with:
console.log("ΑΣ".toLowerCase()); // node: "ας", perry: "ασ"
console.log("ΟΔΥΣΣΕΥΣ".toLowerCase()); // node ends in ς, perry ends in σ
Root cause (source-read, not yet a fix)
case_convert in crates/perry-runtime/src/string/slice_ops.rs maps each decoded scalar via Rust's char::to_lowercase(), which only implements Unicode's unconditional simple/full lowercase mappings. The Final_Sigma rule in SpecialCasing.txt is conditional — it depends on the surrounding context (whether the sigma is preceded by a cased letter and not followed by one) — and Rust's char case-mapping API has no way to express that; it has to be implemented as a pass over the decoded scalar sequence with lookahead/lookbehind, not a per-character map.
Scope note
Not part of #10090 (the ASCII fast-path issue) — Greek text is non-ASCII, so it takes the same scalar case_convert path before and after that fix; this bug predates it and is unaffected by it. Discovered while writing #10090's regression test (test-files/test_gap_10090_string_case_ascii_fastpath.ts), which deliberately excludes final-sigma assertions with a comment pointing here.
What happened
String.prototype.toLowerCase()does not apply the UnicodeFinal_Sigmaconditional casing rule: a Greek capital sigma (Σ, U+03A3) at the end of a word must lowercase to final sigma (ς, U+03C2), not medial sigma (σ, U+03C3). Node (and the spec, viaSpecialCasing.txt's conditionalFinal_Sigmarule) gets this right; Perry always produces medial sigma.Reproduced on current
mainwith:Root cause (source-read, not yet a fix)
case_convertincrates/perry-runtime/src/string/slice_ops.rsmaps each decoded scalar via Rust'schar::to_lowercase(), which only implements Unicode's unconditional simple/full lowercase mappings. TheFinal_Sigmarule inSpecialCasing.txtis conditional — it depends on the surrounding context (whether the sigma is preceded by a cased letter and not followed by one) — and Rust'scharcase-mapping API has no way to express that; it has to be implemented as a pass over the decoded scalar sequence with lookahead/lookbehind, not a per-character map.Scope note
Not part of #10090 (the ASCII fast-path issue) — Greek text is non-ASCII, so it takes the same scalar
case_convertpath before and after that fix; this bug predates it and is unaffected by it. Discovered while writing #10090's regression test (test-files/test_gap_10090_string_case_ascii_fastpath.ts), which deliberately excludes final-sigma assertions with a comment pointing here.