diff --git a/src/unicode/letter.go b/src/unicode/letter.go index 4d9fc67165..8be9a7b7c9 100644 --- a/src/unicode/letter.go +++ b/src/unicode/letter.go @@ -206,9 +206,10 @@ func IsTitle(r rune) bool { } // to maps the rune using the specified case mapping. -func to(_case int, r rune, caseRange []CaseRange) rune { +// It additionally reports whether caseRange contained a mapping for r. +func to(_case int, r rune, caseRange []CaseRange) (mappedRune rune, foundMapping bool) { if _case < 0 || MaxCase <= _case { - return ReplacementChar // as reasonable an error as any + return ReplacementChar, false // as reasonable an error as any } // binary search over ranges lo := 0 @@ -229,9 +230,9 @@ func to(_case int, r rune, caseRange []CaseRange) rune { // bit in the sequence offset. // The constants UpperCase and TitleCase are even while LowerCase // is odd so we take the low bit from _case. - return rune(cr.Lo) + ((r-rune(cr.Lo))&^1 | rune(_case&1)) + return rune(cr.Lo) + ((r-rune(cr.Lo))&^1 | rune(_case&1)), true } - return r + delta + return r + delta, true } if r < rune(cr.Lo) { hi = m @@ -239,12 +240,13 @@ func to(_case int, r rune, caseRange []CaseRange) rune { lo = m + 1 } } - return r + return r, false } // To maps the rune to the specified case: UpperCase, LowerCase, or TitleCase. func To(_case int, r rune) rune { - return to(_case, r, CaseRanges) + r, _ = to(_case, r, CaseRanges) + return r } // ToUpper maps the rune to upper case. @@ -282,8 +284,8 @@ func ToTitle(r rune) rune { // ToUpper maps the rune to upper case giving priority to the special mapping. func (special SpecialCase) ToUpper(r rune) rune { - r1 := to(UpperCase, r, []CaseRange(special)) - if r1 == r { + r1, hadMapping := to(UpperCase, r, []CaseRange(special)) + if r1 == r && !hadMapping { r1 = ToUpper(r) } return r1 @@ -291,8 +293,8 @@ func (special SpecialCase) ToUpper(r rune) rune { // ToTitle maps the rune to title case giving priority to the special mapping. func (special SpecialCase) ToTitle(r rune) rune { - r1 := to(TitleCase, r, []CaseRange(special)) - if r1 == r { + r1, hadMapping := to(TitleCase, r, []CaseRange(special)) + if r1 == r && !hadMapping { r1 = ToTitle(r) } return r1 @@ -300,8 +302,8 @@ func (special SpecialCase) ToTitle(r rune) rune { // ToLower maps the rune to lower case giving priority to the special mapping. func (special SpecialCase) ToLower(r rune) rune { - r1 := to(LowerCase, r, []CaseRange(special)) - if r1 == r { + r1, hadMapping := to(LowerCase, r, []CaseRange(special)) + if r1 == r && !hadMapping { r1 = ToLower(r) } return r1 diff --git a/src/unicode/letter_test.go b/src/unicode/letter_test.go index 3fe72ff13d..19ee535d57 100644 --- a/src/unicode/letter_test.go +++ b/src/unicode/letter_test.go @@ -9,6 +9,7 @@ import ( "fmt" "runtime" "sort" + "strings" "testing" . "unicode" ) @@ -551,3 +552,14 @@ func TestLatinOffset(t *testing.T) { } } } + +func TestSpecialCaseNoMapping(t *testing.T) { + // Issue 25636 + // no change for rune 'A', zero delta, under upper/lower/title case change. + var noChangeForCapitalA = CaseRange{'A', 'A', [MaxCase]rune{0, 0, 0}} + got := strings.ToLowerSpecial(SpecialCase([]CaseRange{noChangeForCapitalA}), "ABC") + want := "Abc" + if got != want { + t.Errorf("got %q; want %q", got, want) + } +}