Skip to content

toLowerCase() does not apply the Greek Final_Sigma conditional casing rule (Σ at word end → σ instead of ς) #10116

Description

@proggeramlug

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.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions