diff --git a/src/Compilers/CSharp/Portable/Parser/QuickScanner.cs b/src/Compilers/CSharp/Portable/Parser/QuickScanner.cs index 4acec774daedb..8c409c7588fa3 100644 --- a/src/Compilers/CSharp/Portable/Parser/QuickScanner.cs +++ b/src/Compilers/CSharp/Portable/Parser/QuickScanner.cs @@ -206,14 +206,14 @@ private SyntaxToken QuickScanSyntaxToken() //localize frequently accessed fields var charWindow = TextWindow.CharacterWindow; - var charPropLength = s_charProperties.Length; + var charPropLength = CharProperties.Length; for (; i < n; i++) { char c = charWindow[i]; int uc = unchecked((int)c); - var flags = uc < charPropLength ? (CharFlags)s_charProperties[uc] : CharFlags.Complex; + var flags = uc < charPropLength ? (CharFlags)CharProperties[uc] : CharFlags.Complex; state = (QuickScanState)s_stateTransitions[(int)state, (int)flags]; // NOTE: that Bad > Done and it is the only state like that @@ -273,7 +273,7 @@ private SyntaxToken CreateQuickToken() // # is marked complex as it may start directives. // PERF: Use byte instead of CharFlags so the compiler can use array literal initialization. // The most natural type choice, Enum arrays, are not blittable due to a CLR limitation. - private static readonly byte[] s_charProperties = new[] + private static ReadOnlySpan CharProperties => new[] { // 0 .. 31 (byte)CharFlags.Complex, (byte)CharFlags.Complex, (byte)CharFlags.Complex, (byte)CharFlags.Complex, (byte)CharFlags.Complex, (byte)CharFlags.Complex, (byte)CharFlags.Complex, (byte)CharFlags.Complex, diff --git a/src/Compilers/Core/Portable/PEWriter/InstructionOperandTypes.cs b/src/Compilers/Core/Portable/PEWriter/InstructionOperandTypes.cs index 4b34937925f21..dee800b95225f 100644 --- a/src/Compilers/Core/Portable/PEWriter/InstructionOperandTypes.cs +++ b/src/Compilers/Core/Portable/PEWriter/InstructionOperandTypes.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Immutable; using System.Reflection.Emit; @@ -23,7 +24,7 @@ internal static OperandType ReadOperandType(ImmutableArray il, ref int pos } // internal for testing - internal static readonly byte[] OneByte = new byte[] + internal static ReadOnlySpan OneByte => new byte[] { (byte)OperandType.InlineNone, // nop (byte)OperandType.InlineNone, // break @@ -283,7 +284,7 @@ internal static OperandType ReadOperandType(ImmutableArray il, ref int pos }; // internal for testing - internal static readonly byte[] TwoByte = new byte[] + internal static ReadOnlySpan TwoByte => new byte[] { (byte)OperandType.InlineNone, // arglist (0xfe 0x00) (byte)OperandType.InlineNone, // ceq diff --git a/src/Compilers/Core/Portable/Xml/XmlCharType.cs b/src/Compilers/Core/Portable/Xml/XmlCharType.cs index 79d9ff8d02304..abd100cb5e31f 100644 --- a/src/Compilers/Core/Portable/Xml/XmlCharType.cs +++ b/src/Compilers/Core/Portable/Xml/XmlCharType.cs @@ -474,7 +474,7 @@ internal static class XmlCharType 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0x00, 0x00, }; - private static byte charProperties(char i) + private static byte GetCharProperties(char i) { // The index entry, table, identifies the start of the appropriate 256-entry table within s_charProperties byte table = s_charPropertiesIndex[i >> innerSizeBits]; @@ -910,7 +910,7 @@ private static byte charProperties(char i) public static bool IsWhiteSpace(char ch) { - return (charProperties(ch) & fWhitespace) != 0; + return (GetCharProperties(ch) & fWhitespace) != 0; } public static bool IsExtender(char ch) @@ -920,7 +920,7 @@ public static bool IsExtender(char ch) public static bool IsNCNameSingleChar(char ch) { - return (charProperties(ch) & fNCNameSC) != 0; + return (GetCharProperties(ch) & fNCNameSC) != 0; } #if XML10_FIFTH_EDITION @@ -955,7 +955,7 @@ public static bool IsNCNameLowSurrogateChar(char lowChar) public static bool IsStartNCNameSingleChar(char ch) { - return (charProperties(ch) & fNCStartNameSC) != 0; + return (GetCharProperties(ch) & fNCStartNameSC) != 0; } #if XML10_FIFTH_EDITION @@ -982,7 +982,7 @@ public static bool IsStartNameSingleChar(char ch) public static bool IsCharData(char ch) { - return (charProperties(ch) & fCharData) != 0; + return (GetCharProperties(ch) & fCharData) != 0; } // [13] PubidChar ::= #x20 | #xD | #xA | [a-zA-Z0-9] | [-'()+,./:=?;!*#@$_%] Section 2.3 of spec @@ -998,26 +998,26 @@ public static bool IsPubidChar(char ch) // TextChar = CharData - { 0xA, 0xD, '<', '&', ']' } internal static bool IsTextChar(char ch) { - return (charProperties(ch) & fText) != 0; + return (GetCharProperties(ch) & fText) != 0; } // AttrValueChar = CharData - { 0xA, 0xD, 0x9, '<', '>', '&', '\'', '"' } internal static bool IsAttributeValueChar(char ch) { - return (charProperties(ch) & fAttrValue) != 0; + return (GetCharProperties(ch) & fAttrValue) != 0; } // XML 1.0 Fourth Edition definitions // public static bool IsLetter(char ch) { - return (charProperties(ch) & fLetter) != 0; + return (GetCharProperties(ch) & fLetter) != 0; } // This method uses the XML 4th edition name character ranges public static bool IsNCNameCharXml4e(char ch) { - return (charProperties(ch) & fNCNameXml4e) != 0; + return (GetCharProperties(ch) & fNCNameXml4e) != 0; } // This method uses the XML 4th edition name character ranges @@ -1089,7 +1089,7 @@ internal static int IsOnlyWhitespaceWithPos(string str) { for (int i = 0; i < str.Length; i++) { - if ((charProperties(str[i]) & fWhitespace) == 0) + if ((GetCharProperties(str[i]) & fWhitespace) == 0) { return i; } @@ -1104,7 +1104,7 @@ internal static int IsOnlyCharData(string str) { for (int i = 0; i < str.Length; i++) { - if ((charProperties(str[i]) & fCharData) == 0) + if ((GetCharProperties(str[i]) & fCharData) == 0) { if (i + 1 >= str.Length || !(XmlCharType.IsHighSurrogate(str[i]) && XmlCharType.IsLowSurrogate(str[i + 1]))) { diff --git a/src/Compilers/Test/Core/Assert/AssertEx.cs b/src/Compilers/Test/Core/Assert/AssertEx.cs index 110bbffc1b1c6..6b83f9b7fd64e 100644 --- a/src/Compilers/Test/Core/Assert/AssertEx.cs +++ b/src/Compilers/Test/Core/Assert/AssertEx.cs @@ -229,6 +229,22 @@ public static void Equal( Assert.True(false, GetAssertMessage(expected, actual, comparer, message, itemInspector, itemSeparator, expectedValueSourcePath, expectedValueSourceLine)); } + public static void Equal( + ReadOnlySpan expected, + ReadOnlySpan actual, + IEqualityComparer comparer = null, + string message = null, + string itemSeparator = null, + Func itemInspector = null, + string expectedValueSourcePath = null, + int expectedValueSourceLine = 0) + { + if (SequenceEqual(expected, actual, comparer)) + return; + + Assert.True(false, GetAssertMessage(expected, actual, comparer, message, itemInspector, itemSeparator, expectedValueSourcePath, expectedValueSourceLine)); + } + /// /// Asserts that two strings are equal, and prints a diff between the two if they are not. /// @@ -326,6 +342,22 @@ private static bool SequenceEqual(IEnumerable expected, IEnumerable act return true; } + private static bool SequenceEqual(ReadOnlySpan expected, ReadOnlySpan actual, IEqualityComparer comparer = null) + { + if (expected.Length != actual.Length) + return false; + + for (int i = 0; i < expected.Length; i++) + { + if (!(comparer is not null ? comparer.Equals(expected[i], actual[i]) : AssertEqualityComparer.Equals(expected[i], actual[i]))) + { + return false; + } + } + + return true; + } + public static void SetEqual(IEnumerable expected, IEnumerable actual, IEqualityComparer comparer = null, string message = null, string itemSeparator = "\r\n", Func itemInspector = null) { var indexes = new Dictionary(comparer); @@ -663,6 +695,86 @@ public static string GetAssertMessage( return message.ToString(); } + public static string GetAssertMessage( + ReadOnlySpan expected, + ReadOnlySpan actual, + IEqualityComparer comparer = null, + string prefix = null, + Func itemInspector = null, + string itemSeparator = null, + string expectedValueSourcePath = null, + int expectedValueSourceLine = 0) + { + if (itemInspector == null) + { + if (typeof(T) == typeof(byte)) + { + itemInspector = b => $"0x{b:X2}"; + } + else + { + itemInspector = new Func(obj => (obj != null) ? obj.ToString() : ""); + } + } + + if (itemSeparator == null) + { + if (typeof(T) == typeof(byte)) + { + itemSeparator = ", "; + } + else + { + itemSeparator = "," + Environment.NewLine; + } + } + + const int maxDisplayedExpectedEntries = 10; + var expectedString = join(itemSeparator, expected[..Math.Min(expected.Length, maxDisplayedExpectedEntries)], itemInspector); + var actualString = join(itemSeparator, actual, itemInspector); + + var message = new StringBuilder(); + + if (!string.IsNullOrEmpty(prefix)) + { + message.AppendLine(prefix); + message.AppendLine(); + } + + message.AppendLine("Expected:"); + message.AppendLine(expectedString); + if (expected.Length > maxDisplayedExpectedEntries) + { + message.AppendLine("... truncated ..."); + } + + message.AppendLine("Actual:"); + message.AppendLine(actualString); + message.AppendLine("Differences:"); + message.AppendLine(DiffUtil.DiffReport(expected.ToArray(), actual.ToArray(), itemSeparator, comparer, itemInspector)); + + if (TryGenerateExpectedSourceFileAndGetDiffLink(actualString, expected.Length, expectedValueSourcePath, expectedValueSourceLine, out var link)) + { + message.AppendLine(link); + } + + return message.ToString(); + + static string join(string itemSeparator, ReadOnlySpan items, Func itemInspector) + { + var result = new StringBuilder(); + var iter = items.GetEnumerator(); + + if (iter.MoveNext()) + result.Append(itemInspector(iter.Current)); + + while (iter.MoveNext()) + result.Append($"{itemSeparator}{itemInspector(iter.Current)}"); + + return result.ToString(); + } + } + internal static bool TryGenerateExpectedSourceFileAndGetDiffLink(string actualString, int expectedLineCount, string expectedValueSourcePath, int expectedValueSourceLine, out string link) { // add a link to a .cmd file that opens a diff tool: