diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/TextInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/TextInfo.cs index 83686710940462..f9571bc811297c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/TextInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/TextInfo.cs @@ -171,24 +171,18 @@ internal static char ToLowerInvariant(char c) public string ToLower(string str) { ArgumentNullException.ThrowIfNull(str); + return ChangeCaseCommon(this, str); + } - if (GlobalizationMode.Invariant) - { - return InvariantModeCasing.ToLower(str); - } - - return ChangeCaseCommon(str); + internal static string ToLowerInvariant(string str) + { + ArgumentNullException.ThrowIfNull(str); + return ChangeCaseCommon(null, str); } internal void ToLower(ReadOnlySpan source, Span destination) { - if (GlobalizationMode.Invariant) - { - InvariantModeCasing.ToLower(source, destination); - return; - } - - ChangeCaseCommon(source, destination); + ChangeCaseCommon(this, source, destination); } private unsafe char ChangeCase(char c, bool toUpper) @@ -221,20 +215,19 @@ internal static char ToUpperOrdinal(char c) internal void ChangeCaseToLower(ReadOnlySpan source, Span destination) { Debug.Assert(destination.Length >= source.Length); - ChangeCaseCommon(source, destination); + ChangeCaseCommon(this, source, destination); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void ChangeCaseToUpper(ReadOnlySpan source, Span destination) { Debug.Assert(destination.Length >= source.Length); - ChangeCaseCommon(source, destination); + ChangeCaseCommon(this, source, destination); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private unsafe void ChangeCaseCommon(ReadOnlySpan source, Span destination) where TConversion : struct + private static unsafe void ChangeCaseCommon(TextInfo? instance, ReadOnlySpan source, Span destination) where TConversion : struct { - Debug.Assert(!GlobalizationMode.Invariant); Debug.Assert(typeof(TConversion) == typeof(ToUpperConversion) || typeof(TConversion) == typeof(ToLowerConversion)); if (source.IsEmpty) @@ -245,7 +238,8 @@ private unsafe void ChangeCaseCommon(ReadOnlySpan source, Spa bool toUpper = typeof(TConversion) == typeof(ToUpperConversion); // JIT will treat this as a constant in release builds int charsConsumed = 0; - if (IsAsciiCasingSameAsInvariant) + // instance being null indicates the invariant culture where IsAsciiCasingSameAsInvariant is always true. + if (instance == null || instance.IsAsciiCasingSameAsInvariant) { OperationStatus operationStatus = toUpper ? Ascii.ToUpper(source, destination, out charsConsumed) @@ -258,19 +252,35 @@ private unsafe void ChangeCaseCommon(ReadOnlySpan source, Spa } } + if (GlobalizationMode.Invariant) + { + if (toUpper) + { + InvariantModeCasing.ToUpper(source, destination); + } + else + { + InvariantModeCasing.ToLower(source, destination); + } + return; + } + + // instance being null means it's Invariant + instance ??= Invariant; + fixed (char* pSource = &MemoryMarshal.GetReference(source)) fixed (char* pDestination = &MemoryMarshal.GetReference(destination)) { - ChangeCaseCore(pSource + charsConsumed, source.Length - charsConsumed, pDestination + charsConsumed, destination.Length - charsConsumed, toUpper); + instance.ChangeCaseCore(pSource + charsConsumed, source.Length - charsConsumed, + pDestination + charsConsumed, destination.Length - charsConsumed, toUpper); } } - private unsafe string ChangeCaseCommon(string source) where TConversion : struct + private static unsafe string ChangeCaseCommon(TextInfo? instance, string source) where TConversion : struct { Debug.Assert(typeof(TConversion) == typeof(ToUpperConversion) || typeof(TConversion) == typeof(ToLowerConversion)); bool toUpper = typeof(TConversion) == typeof(ToUpperConversion); // JIT will treat this as a constant in release builds - Debug.Assert(!GlobalizationMode.Invariant); Debug.Assert(source != null); // If the string is empty, we're done. @@ -286,7 +296,9 @@ private unsafe string ChangeCaseCommon(string source) where TConver // If this culture's casing for ASCII is the same as invariant, try to take // a fast path that'll work in managed code and ASCII rather than calling out // to the OS for culture-aware casing. - if (IsAsciiCasingSameAsInvariant) + // + // instance being null indicates the invariant culture where IsAsciiCasingSameAsInvariant is always true. + if (instance == null || instance.IsAsciiCasingSameAsInvariant) { // Read 2 chars (one 32-bit integer) at a time @@ -341,13 +353,18 @@ private unsafe string ChangeCaseCommon(string source) where TConver source.AsSpan(0, (int)currIdx).CopyTo(resultSpan); // and re-run the fast span-based logic over the remainder of the data - ChangeCaseCommon(source.AsSpan((int)currIdx), resultSpan.Slice((int)currIdx)); + ChangeCaseCommon(instance, source.AsSpan((int)currIdx), resultSpan.Slice((int)currIdx)); return result; } } NotAscii: { + if (GlobalizationMode.Invariant) + { + return toUpper ? InvariantModeCasing.ToUpper(source) : InvariantModeCasing.ToLower(source); + } + // We reached non-ASCII data *or* the requested culture doesn't map ASCII data the same way as the invariant culture. // In either case we need to fall back to the localization tables. @@ -360,10 +377,13 @@ private unsafe string ChangeCaseCommon(string source) where TConver source.AsSpan(0, (int)currIdx).CopyTo(resultSpan); } + // instance being null means it's Invariant + instance ??= Invariant; + // and run the culture-aware logic over the remainder of the data fixed (char* pResult = result) { - ChangeCaseCore(pSource + currIdx, source.Length - (int)currIdx, pResult + currIdx, result.Length - (int)currIdx, toUpper); + instance.ChangeCaseCore(pSource + currIdx, source.Length - (int)currIdx, pResult + currIdx, result.Length - (int)currIdx, toUpper); } return result; } @@ -453,24 +473,18 @@ internal static char ToUpperInvariant(char c) public string ToUpper(string str) { ArgumentNullException.ThrowIfNull(str); + return ChangeCaseCommon(this, str); + } - if (GlobalizationMode.Invariant) - { - return InvariantModeCasing.ToUpper(str); - } - - return ChangeCaseCommon(str); + internal static string ToUpperInvariant(string str) + { + ArgumentNullException.ThrowIfNull(str); + return ChangeCaseCommon(null, str); } internal void ToUpper(ReadOnlySpan source, Span destination) { - if (GlobalizationMode.Invariant) - { - InvariantModeCasing.ToUpper(source, destination); - return; - } - - ChangeCaseCommon(source, destination); + ChangeCaseCommon(this, source, destination); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs b/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs index 774f5c49d46e2f..7acc23d53c6535 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs @@ -2365,7 +2365,7 @@ public string ToLower(CultureInfo? culture) // Creates a copy of this string in lower case based on invariant culture. public string ToLowerInvariant() { - return TextInfo.Invariant.ToLower(this); + return TextInfo.ToLowerInvariant(this); } public string ToUpper() => ToUpper(null); @@ -2380,7 +2380,7 @@ public string ToUpper(CultureInfo? culture) // Creates a copy of this string in upper case based on invariant culture. public string ToUpperInvariant() { - return TextInfo.Invariant.ToUpper(this); + return TextInfo.ToUpperInvariant(this); } // Trims the whitespace from both ends of the string. Whitespace is defined by diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/TrimmingTests/InvariantGlobalizationTrue.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/TrimmingTests/InvariantGlobalizationTrue.cs index 27ab1b136b5a4f..0f121c9c943d81 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/TrimmingTests/InvariantGlobalizationTrue.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/TrimmingTests/InvariantGlobalizationTrue.cs @@ -11,10 +11,31 @@ /// class Program { + private static string Str1 = "aaaaBBBB"; + private static string Str2 = "ccccDDDD"; + private static string Str3; + static int Main(string[] args) { const BindingFlags allStatics = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static; + Str3 = Str1.ToLowerInvariant() + Str2.ToUpperInvariant(); + + Type textInfo = GetCoreLibType("System.Globalization.TextInfo"); + if (textInfo != null) + { + string[] methodsShouldBeGone = ["NlsChangeCase", "ChangeCaseCore", "ChangeCaseNative", "IcuChangeCase"]; + + foreach (MethodInfo method in textInfo.GetMethods(BindingFlags.Instance | allStatics)) + { + if (methodsShouldBeGone.Contains(method.Name)) + { + Console.WriteLine($"Member '{method.Name}' was not trimmed from GlobalizationMode, but should have been."); + return -2; + } + } + } + try { CultureInfo.CurrentCulture = new CultureInfo("tr-TR"); @@ -40,7 +61,7 @@ static int Main(string[] args) return 100; } - + // Ensure the internal GlobalizationMode class is trimmed correctly. Type globalizationMode = GetCoreLibType("System.Globalization.GlobalizationMode");