diff --git a/src/Orleans.Serialization/TypeSystem/RuntimeTypeNameParser.cs b/src/Orleans.Serialization/TypeSystem/RuntimeTypeNameParser.cs index c00b50db5e..3aff7d65e3 100644 --- a/src/Orleans.Serialization/TypeSystem/RuntimeTypeNameParser.cs +++ b/src/Orleans.Serialization/TypeSystem/RuntimeTypeNameParser.cs @@ -109,7 +109,7 @@ private static TypeSpec ParseInternal(ref BufferReader input) } } - coreType = new TupleTypeSpec(elements.ToArray(), input.TotalGenericArity); + coreType = new TupleTypeSpec([.. elements], input.TotalGenericArity); } else { @@ -378,7 +378,7 @@ public void ConsumeCharacter(char assertChar) var c = Input[Index]; if (assertChar != c) { - ThrowUnexpectedCharacter(assertChar, c); + ThrowUnexpectedCharacter(Input, Index, assertChar, c); } ++Index; @@ -396,7 +396,13 @@ public void ConsumeWhitespace() } } - private static void ThrowUnexpectedCharacter(char expected, char actual) => throw new InvalidOperationException($"Encountered unexpected character. Expected '{expected}', actual '{actual}'."); + private static void ThrowUnexpectedCharacter(ReadOnlySpan value, int position, char expected, char actual) + { + var valueString = new string(value); + var posString = position > 0 ? new string(' ', position) : ""; + var message = $"Encountered unexpected character. Expected '{expected}', actual '{actual}' in string:\n> {valueString}\n> {posString}^"; + throw new InvalidOperationException(message); + } private static void ThrowEndOfInput() => throw new InvalidOperationException("Tried to read past the end of the input"); diff --git a/test/NonSilo.Tests/RuntimeTypeNameFormatterTests.cs b/test/NonSilo.Tests/RuntimeTypeNameFormatterTests.cs index 30ab785d5d..809fe9ca75 100644 --- a/test/NonSilo.Tests/RuntimeTypeNameFormatterTests.cs +++ b/test/NonSilo.Tests/RuntimeTypeNameFormatterTests.cs @@ -70,7 +70,6 @@ public void FormattedTypeNamesAreRecoverable() /// [Fact] public void ParsedTypeNamesAreIdenticalToFormattedNames() - { foreach (var type in _types) { @@ -85,7 +84,17 @@ public void ParsedTypeNamesAreIdenticalToFormattedNames() _output.WriteLine($"Reparsed : {reparsed}"); Assert.Equal(formatted, reparsed.Format()); } - } + } + + [Fact] + public void InvalidNamesThrowDescriptiveErrorMessage() + { + var input = "MalformedName[`"; + var exception = Assert.Throws(() => RuntimeTypeNameParser.Parse(input)); + _output.WriteLine(exception.Message); + Assert.Contains(input, exception.Message); + Assert.Contains("^", exception.Message); // Position indicator + } public class Inner {