diff --git a/src/libraries/System.Formats.Nrbf/src/Resources/Strings.resx b/src/libraries/System.Formats.Nrbf/src/Resources/Strings.resx index 22ce2808f24062..349405150c65e9 100644 --- a/src/libraries/System.Formats.Nrbf/src/Resources/Strings.resx +++ b/src/libraries/System.Formats.Nrbf/src/Resources/Strings.resx @@ -159,4 +159,7 @@ Only arrays with zero offsets are supported. + + Invalid assembly name: `{0}`. + \ No newline at end of file diff --git a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/ArraySingleObjectRecord.cs b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/ArraySingleObjectRecord.cs index de77d3e0f84fac..37e94842719a90 100644 --- a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/ArraySingleObjectRecord.cs +++ b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/ArraySingleObjectRecord.cs @@ -16,14 +16,12 @@ namespace System.Formats.Nrbf; /// internal sealed class ArraySingleObjectRecord : SZArrayRecord { - private static TypeName? s_typeName; - private ArraySingleObjectRecord(ArrayInfo arrayInfo) : base(arrayInfo) => Records = []; public override SerializationRecordType RecordType => SerializationRecordType.ArraySingleObject; public override TypeName TypeName - => s_typeName ??= TypeName.Parse(("System.Object[], " + TypeNameExtensions.CoreLibAssemblyName).AsSpan()); + => TypeNameHelpers.GetPrimitiveSZArrayTypeName(TypeNameHelpers.ObjectPrimitiveType); private List Records { get; } diff --git a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/ArraySinglePrimitiveRecord.cs b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/ArraySinglePrimitiveRecord.cs index a09ca6b7e48d27..ee3a7916b069cf 100644 --- a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/ArraySinglePrimitiveRecord.cs +++ b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/ArraySinglePrimitiveRecord.cs @@ -24,8 +24,6 @@ namespace System.Formats.Nrbf; internal sealed class ArraySinglePrimitiveRecord : SZArrayRecord where T : unmanaged { - private static TypeName? s_typeName; - internal ArraySinglePrimitiveRecord(ArrayInfo arrayInfo, IReadOnlyList values) : base(arrayInfo) { Values = values; @@ -35,8 +33,7 @@ internal ArraySinglePrimitiveRecord(ArrayInfo arrayInfo, IReadOnlyList values public override SerializationRecordType RecordType => SerializationRecordType.ArraySinglePrimitive; /// - public override TypeName TypeName - => s_typeName ??= TypeName.Parse((typeof(T[]).FullName + "," + TypeNameExtensions.CoreLibAssemblyName).AsSpan()); + public override TypeName TypeName => TypeNameHelpers.GetPrimitiveSZArrayTypeName(TypeNameHelpers.GetPrimitiveType()); internal IReadOnlyList Values { get; } diff --git a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/ArraySingleStringRecord.cs b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/ArraySingleStringRecord.cs index 4d7f5ace476791..de248bcef76755 100644 --- a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/ArraySingleStringRecord.cs +++ b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/ArraySingleStringRecord.cs @@ -16,15 +16,12 @@ namespace System.Formats.Nrbf; /// internal sealed class ArraySingleStringRecord : SZArrayRecord { - private static TypeName? s_typeName; - private ArraySingleStringRecord(ArrayInfo arrayInfo) : base(arrayInfo) => Records = []; public override SerializationRecordType RecordType => SerializationRecordType.ArraySingleString; /// - public override TypeName TypeName - => s_typeName ??= TypeName.Parse(("System.String[], " + TypeNameExtensions.CoreLibAssemblyName).AsSpan()); + public override TypeName TypeName => TypeNameHelpers.GetPrimitiveSZArrayTypeName(PrimitiveType.String); private List Records { get; } diff --git a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/BinaryLibraryRecord.cs b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/BinaryLibraryRecord.cs index d7a92293fdd2bc..ccd39922e23fba 100644 --- a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/BinaryLibraryRecord.cs +++ b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/BinaryLibraryRecord.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Formats.Nrbf.Utils; using System.IO; using System.Reflection.Metadata; @@ -15,7 +16,13 @@ namespace System.Formats.Nrbf; /// internal sealed class BinaryLibraryRecord : SerializationRecord { - private BinaryLibraryRecord(SerializationRecordId libraryId, string libraryName) + private BinaryLibraryRecord(SerializationRecordId libraryId, string rawLibraryName) + { + Id = libraryId; + RawLibraryName = rawLibraryName; + } + + private BinaryLibraryRecord(SerializationRecordId libraryId, AssemblyNameInfo libraryName) { Id = libraryId; LibraryName = libraryName; @@ -32,11 +39,27 @@ public override TypeName TypeName } } - internal string LibraryName { get; } + internal string? RawLibraryName { get; } + + internal AssemblyNameInfo? LibraryName { get; } /// public override SerializationRecordId Id { get; } - internal static BinaryLibraryRecord Decode(BinaryReader reader) - => new(SerializationRecordId.Decode(reader), reader.ReadString()); + internal static BinaryLibraryRecord Decode(BinaryReader reader, PayloadOptions options) + { + SerializationRecordId id = SerializationRecordId.Decode(reader); + string rawName = reader.ReadString(); + + if (AssemblyNameInfo.TryParse(rawName.AsSpan(), out AssemblyNameInfo? assemblyNameInfo)) + { + return new BinaryLibraryRecord(id, assemblyNameInfo); + } + else if (!options.UndoTruncatedTypeNames) + { + ThrowHelper.ThrowInvalidAssemblyName(rawName); + } + + return new BinaryLibraryRecord(id, rawName); + } } diff --git a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/MemberTypeInfo.cs b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/MemberTypeInfo.cs index 9e6c2445877e0b..9843a0b71f04c0 100644 --- a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/MemberTypeInfo.cs +++ b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/MemberTypeInfo.cs @@ -142,63 +142,26 @@ internal TypeName GetArrayTypeName(ArrayInfo arrayInfo) { (BinaryType binaryType, object? additionalInfo) = Infos[0]; - switch (binaryType) + TypeName elementTypeName = binaryType switch { - case BinaryType.String: - return typeof(string).BuildCoreLibArrayTypeName(arrayInfo.Rank); - case BinaryType.StringArray: - return typeof(string[]).BuildCoreLibArrayTypeName(arrayInfo.Rank); - case BinaryType.Object: - return typeof(object).BuildCoreLibArrayTypeName(arrayInfo.Rank); - case BinaryType.ObjectArray: - return typeof(object[]).BuildCoreLibArrayTypeName(arrayInfo.Rank); - case BinaryType.Primitive: - Type primitiveType = ((PrimitiveType)additionalInfo!) switch - { - PrimitiveType.Boolean => typeof(bool), - PrimitiveType.Byte => typeof(byte), - PrimitiveType.Char => typeof(char), - PrimitiveType.Decimal => typeof(decimal), - PrimitiveType.Double => typeof(double), - PrimitiveType.Int16 => typeof(short), - PrimitiveType.Int32 => typeof(int), - PrimitiveType.Int64 => typeof(long), - PrimitiveType.SByte => typeof(sbyte), - PrimitiveType.Single => typeof(float), - PrimitiveType.TimeSpan => typeof(TimeSpan), - PrimitiveType.DateTime => typeof(DateTime), - PrimitiveType.UInt16 => typeof(ushort), - PrimitiveType.UInt32 => typeof(uint), - _ => typeof(ulong), - }; - - return primitiveType.BuildCoreLibArrayTypeName(arrayInfo.Rank); - case BinaryType.PrimitiveArray: - Type primitiveArrayType = ((PrimitiveType)additionalInfo!) switch - { - PrimitiveType.Boolean => typeof(bool[]), - PrimitiveType.Byte => typeof(byte[]), - PrimitiveType.Char => typeof(char[]), - PrimitiveType.Decimal => typeof(decimal[]), - PrimitiveType.Double => typeof(double[]), - PrimitiveType.Int16 => typeof(short[]), - PrimitiveType.Int32 => typeof(int[]), - PrimitiveType.Int64 => typeof(long[]), - PrimitiveType.SByte => typeof(sbyte[]), - PrimitiveType.Single => typeof(float[]), - PrimitiveType.TimeSpan => typeof(TimeSpan[]), - PrimitiveType.DateTime => typeof(DateTime[]), - PrimitiveType.UInt16 => typeof(ushort[]), - PrimitiveType.UInt32 => typeof(uint[]), - _ => typeof(ulong[]), - }; - - return primitiveArrayType.BuildCoreLibArrayTypeName(arrayInfo.Rank); - case BinaryType.SystemClass: - return ((TypeName)additionalInfo!).BuildArrayTypeName(arrayInfo.Rank); - default: - Debug.Assert(binaryType is BinaryType.Class, "The parsers should reject other inputs"); - return (((ClassTypeInfo)additionalInfo!).TypeName).BuildArrayTypeName(arrayInfo.Rank); - } + BinaryType.String => TypeNameHelpers.GetPrimitiveTypeName(PrimitiveType.String), + BinaryType.StringArray => TypeNameHelpers.GetPrimitiveSZArrayTypeName(PrimitiveType.String), + BinaryType.Primitive => TypeNameHelpers.GetPrimitiveTypeName((PrimitiveType)additionalInfo!), + BinaryType.PrimitiveArray => TypeNameHelpers.GetPrimitiveSZArrayTypeName((PrimitiveType)additionalInfo!), + BinaryType.Object => TypeNameHelpers.GetPrimitiveTypeName(TypeNameHelpers.ObjectPrimitiveType), + BinaryType.ObjectArray => TypeNameHelpers.GetPrimitiveSZArrayTypeName(TypeNameHelpers.ObjectPrimitiveType), + BinaryType.SystemClass => (TypeName)additionalInfo!, + BinaryType.Class => ((ClassTypeInfo)additionalInfo!).TypeName, + _ => throw new ArgumentOutOfRangeException(paramName: nameof(binaryType), actualValue: binaryType, message: null) + }; + + // In general, arrayRank == 1 may have two different meanings: + // - [] is a single-dimensional array with a zero lower bound (SZArray), + // - [*] is a single-dimensional array with an arbitrary lower bound (variable bound array). + // Variable bound arrays are not supported by design, so in our case it's always SZArray. + // That is why we don't call TypeName.MakeArrayTypeName(1) because it would create [*] instead of [] name. + return arrayInfo.Rank == 1 + ? elementTypeName.MakeSZArrayTypeName() + : elementTypeName.MakeArrayTypeName(arrayInfo.Rank); } } diff --git a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/NrbfDecoder.cs b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/NrbfDecoder.cs index 43db53fd159e15..a07c567b7a7698 100644 --- a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/NrbfDecoder.cs +++ b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/NrbfDecoder.cs @@ -226,7 +226,7 @@ private static SerializationRecord DecodeNext(BinaryReader reader, RecordMap rec SerializationRecordType.ArraySinglePrimitive => DecodeArraySinglePrimitiveRecord(reader), SerializationRecordType.ArraySingleString => ArraySingleStringRecord.Decode(reader), SerializationRecordType.BinaryArray => BinaryArrayRecord.Decode(reader, recordMap, options), - SerializationRecordType.BinaryLibrary => BinaryLibraryRecord.Decode(reader), + SerializationRecordType.BinaryLibrary => BinaryLibraryRecord.Decode(reader, options), SerializationRecordType.BinaryObjectString => BinaryObjectStringRecord.Decode(reader), SerializationRecordType.ClassWithId => ClassWithIdRecord.Decode(reader, recordMap), SerializationRecordType.ClassWithMembersAndTypes => ClassWithMembersAndTypesRecord.Decode(reader, recordMap, options), diff --git a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/PrimitiveTypeRecordOfT.cs b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/PrimitiveTypeRecordOfT.cs index 72ef801e61eb32..80f5dc54ef1de8 100644 --- a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/PrimitiveTypeRecordOfT.cs +++ b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/PrimitiveTypeRecordOfT.cs @@ -25,8 +25,6 @@ namespace System.Formats.Nrbf; [DebuggerDisplay("{Value}")] public abstract class PrimitiveTypeRecord : PrimitiveTypeRecord { - private static TypeName? s_typeName; - private protected PrimitiveTypeRecord(T value) => Value = value; /// @@ -36,8 +34,7 @@ public abstract class PrimitiveTypeRecord : PrimitiveTypeRecord public new T Value { get; } /// - public override TypeName TypeName - => s_typeName ??= TypeName.Parse(typeof(T).FullName.AsSpan()).WithCoreLibAssemblyName(); + public override TypeName TypeName => TypeNameHelpers.GetPrimitiveTypeName(TypeNameHelpers.GetPrimitiveType()); internal override object? GetValue() => Value; } diff --git a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/SystemClassWithMembersAndTypesRecord.cs b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/SystemClassWithMembersAndTypesRecord.cs index 93095bc32b0007..05d38ec736f10c 100644 --- a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/SystemClassWithMembersAndTypesRecord.cs +++ b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/SystemClassWithMembersAndTypesRecord.cs @@ -38,6 +38,11 @@ internal override (AllowedRecordTypes allowed, PrimitiveType primitiveType) GetN // to get a single primitive value! internal SerializationRecord TryToMapToUserFriendly() { + if (!TypeName.IsSimple) + { + return this; + } + if (MemberValues.Count == 1) { if (HasMember("m_value")) @@ -45,18 +50,18 @@ internal SerializationRecord TryToMapToUserFriendly() return MemberValues[0] switch { // there can be a value match, but no TypeName match - bool value when TypeName.FullName == typeof(bool).FullName => Create(value), - byte value when TypeName.FullName == typeof(byte).FullName => Create(value), - sbyte value when TypeName.FullName == typeof(sbyte).FullName => Create(value), - char value when TypeName.FullName == typeof(char).FullName => Create(value), - short value when TypeName.FullName == typeof(short).FullName => Create(value), - ushort value when TypeName.FullName == typeof(ushort).FullName => Create(value), - int value when TypeName.FullName == typeof(int).FullName => Create(value), - uint value when TypeName.FullName == typeof(uint).FullName => Create(value), - long value when TypeName.FullName == typeof(long).FullName => Create(value), - ulong value when TypeName.FullName == typeof(ulong).FullName => Create(value), - float value when TypeName.FullName == typeof(float).FullName => Create(value), - double value when TypeName.FullName == typeof(double).FullName => Create(value), + bool value when TypeNameMatches(typeof(bool)) => Create(value), + byte value when TypeNameMatches(typeof(byte)) => Create(value), + sbyte value when TypeNameMatches(typeof(sbyte)) => Create(value), + char value when TypeNameMatches(typeof(char)) => Create(value), + short value when TypeNameMatches(typeof(short)) => Create(value), + ushort value when TypeNameMatches(typeof(ushort)) => Create(value), + int value when TypeNameMatches(typeof(int)) => Create(value), + uint value when TypeNameMatches(typeof(uint)) => Create(value), + long value when TypeNameMatches(typeof(long)) => Create(value), + ulong value when TypeNameMatches(typeof(ulong)) => Create(value), + float value when TypeNameMatches(typeof(float)) => Create(value), + double value when TypeNameMatches(typeof(double)) => Create(value), _ => this }; } @@ -65,12 +70,12 @@ internal SerializationRecord TryToMapToUserFriendly() return MemberValues[0] switch { // there can be a value match, but no TypeName match - long value when TypeName.FullName == typeof(IntPtr).FullName => Create(new IntPtr(value)), - ulong value when TypeName.FullName == typeof(UIntPtr).FullName => Create(new UIntPtr(value)), + long value when TypeNameMatches(typeof(IntPtr)) => Create(new IntPtr(value)), + ulong value when TypeNameMatches(typeof(UIntPtr)) => Create(new UIntPtr(value)), _ => this }; } - else if (HasMember("_ticks") && MemberValues[0] is long ticks && TypeName.FullName == typeof(TimeSpan).FullName) + else if (HasMember("_ticks") && MemberValues[0] is long ticks && TypeNameMatches(typeof(TimeSpan))) { return Create(new TimeSpan(ticks)); } diff --git a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/Utils/ThrowHelper.cs b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/Utils/ThrowHelper.cs index de543f2e098cf4..f096bfc736098e 100644 --- a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/Utils/ThrowHelper.cs +++ b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/Utils/ThrowHelper.cs @@ -14,6 +14,9 @@ internal static void ThrowInvalidValue(object value) internal static void ThrowInvalidReference() => throw new SerializationException(SR.Serialization_InvalidReference); + internal static void ThrowInvalidTypeName(string name) + => throw new SerializationException(SR.Format(SR.Serialization_InvalidTypeName, name)); + internal static void ThrowUnexpectedNullRecordCount() => throw new SerializationException(SR.Serialization_UnexpectedNullRecordCount); @@ -23,6 +26,9 @@ internal static void ThrowMaxArrayLength(long limit, long actual) internal static void ThrowArrayContainedNulls() => throw new SerializationException(SR.Serialization_ArrayContainedNulls); + internal static void ThrowInvalidAssemblyName(string rawName) + => throw new SerializationException(SR.Format(SR.Serialization_InvalidAssemblyName, rawName)); + internal static void ThrowEndOfStreamException() => throw new EndOfStreamException(); diff --git a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/Utils/TypeNameExtensions.cs b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/Utils/TypeNameExtensions.cs deleted file mode 100644 index 017f4597bfc2de..00000000000000 --- a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/Utils/TypeNameExtensions.cs +++ /dev/null @@ -1,112 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Buffers; -using System.Diagnostics; -using System.Reflection.Metadata; -using System.Runtime.CompilerServices; -using System.Runtime.Serialization; -using System.Text; - -namespace System.Formats.Nrbf.Utils; - -internal static class TypeNameExtensions -{ - internal const string CoreLibAssemblyName = "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"; - - internal static TypeName ParseNonSystemClassRecordTypeName(this string rawName, BinaryLibraryRecord libraryRecord, PayloadOptions payloadOptions) - { - // Combining type and library name has two goals: - // 1. Handle truncated generic type names that may be present in resources. - // 2. Improve perf by parsing only once. - ArraySegment assemblyQualifiedName = GetAssemblyQualifiedName(rawName, libraryRecord.LibraryName); - TypeName.TryParse(assemblyQualifiedName.AsSpan(), out TypeName? typeName, payloadOptions.TypeNameParseOptions); - ArrayPool.Shared.Return(assemblyQualifiedName.Array!); - - if (typeName is null || (typeName.AssemblyName is null && !payloadOptions.UndoTruncatedTypeNames)) - { - throw new SerializationException(SR.Format(SR.Serialization_InvalidTypeOrAssemblyName, rawName, libraryRecord.LibraryName)); - } - - if (typeName.AssemblyName is null && payloadOptions.UndoTruncatedTypeNames) - { - // Sample invalid input that could lead us here: - // TypeName: System.Collections.Generic.List`1[[System.String - // LibraryName: 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]] - // Since the flag is ON, we know it's mangling and we provide missing information. - typeName = typeName.WithCoreLibAssemblyName(); - } - - return typeName; - } - - internal static TypeName ParseSystemRecordTypeName(this string rawName, PayloadOptions payloadOptions) - { - ArraySegment assemblyQualifiedName = GetAssemblyQualifiedName(rawName, - CoreLibAssemblyName); // We know it's a System Record, so we set the LibraryName to CoreLib - - TypeName.TryParse(assemblyQualifiedName.AsSpan(), out TypeName? typeName, payloadOptions.TypeNameParseOptions); - ArrayPool.Shared.Return(assemblyQualifiedName.Array!); - - return typeName ?? throw new SerializationException(SR.Format(SR.Serialization_InvalidTypeName, rawName)); - } - - internal static TypeName WithCoreLibAssemblyName(this TypeName systemType) - => systemType.WithAssemblyName(CoreLibAssemblyName); - - internal static TypeName WithAssemblyName(this TypeName typeName, string assemblyName) - { - // For ClassWithMembersAndTypesRecord, the TypeName and LibraryName and provided separately, - // and the LibraryName may not be known when parsing TypeName. - // For SystemClassWithMembersAndTypesRecord, the LibraryName is not provided, it's always mscorlib. - // Ideally, we would just create TypeName with new AssemblyNameInfo. - // This will be possible once https://github.com/dotnet/runtime/issues/102263 is done. - - ArraySegment assemblyQualifiedName = GetAssemblyQualifiedName(typeName.FullName, assemblyName); - TypeName result = TypeName.Parse(assemblyQualifiedName.AsSpan()); - ArrayPool.Shared.Return(assemblyQualifiedName.Array!); - - return result; - } - - internal static TypeName BuildCoreLibArrayTypeName(this Type type, int arrayRank) - { - ArraySegment assemblyQualifiedName = GetAssemblyQualifiedName(type.FullName!, CoreLibAssemblyName, arrayRank); - TypeName result = TypeName.Parse(assemblyQualifiedName.AsSpan()); - ArrayPool.Shared.Return(assemblyQualifiedName.Array!); - - return result; - } - - internal static TypeName BuildArrayTypeName(this TypeName typeName, int arrayRank) - { - ArraySegment assemblyQualifiedName = GetAssemblyQualifiedName(typeName.FullName, typeName.AssemblyName!.FullName, arrayRank); - TypeName result = TypeName.Parse(assemblyQualifiedName.AsSpan()); - ArrayPool.Shared.Return(assemblyQualifiedName.Array!); - - return result; - } - - private static ArraySegment GetAssemblyQualifiedName(string typeName, string libraryName, int arrayRank = 0) - { - int arrayLength = arrayRank != 0 ? 2 + arrayRank - 1 : 0; - int length = typeName.Length + arrayLength + 1 + libraryName.Length; - - char[] rented = ArrayPool.Shared.Rent(length); - - typeName.AsSpan().CopyTo(rented); - if (arrayRank != 0) - { - rented[typeName.Length] = '['; - for (int i = 1; i < arrayRank; i++) - { - rented[typeName.Length + i] = ','; - } - rented[typeName.Length + arrayLength - 1] = ']'; - } - rented[typeName.Length + arrayLength] = ','; - libraryName.AsSpan().CopyTo(rented.AsSpan(typeName.Length + arrayLength + 1)); - - return new ArraySegment(rented, 0, length); - } -} diff --git a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/Utils/TypeNameHelpers.cs b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/Utils/TypeNameHelpers.cs new file mode 100644 index 00000000000000..97c3b4e42f68b7 --- /dev/null +++ b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/Utils/TypeNameHelpers.cs @@ -0,0 +1,202 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Buffers; +using System.Diagnostics; +using System.Reflection.Metadata; +using System.Runtime.CompilerServices; +using System.Runtime.Serialization; +using System.Text; + +namespace System.Formats.Nrbf.Utils; + +internal static class TypeNameHelpers +{ + // PrimitiveType does not define Object, IntPtr or UIntPtr + internal const PrimitiveType ObjectPrimitiveType = (PrimitiveType)19; + internal const PrimitiveType IntPtrPrimitiveType = (PrimitiveType)20; + internal const PrimitiveType UIntPtrPrimitiveType = (PrimitiveType)21; + private static readonly TypeName?[] s_primitiveTypeNames = new TypeName?[(int)UIntPtrPrimitiveType + 1]; + private static readonly TypeName?[] s_primitiveSZArrayTypeNames = new TypeName?[(int)UIntPtrPrimitiveType + 1]; + private static AssemblyNameInfo? s_coreLibAssemblyName; + + internal static TypeName GetPrimitiveTypeName(PrimitiveType primitiveType) + { + Debug.Assert(primitiveType is not (PrimitiveType.None or PrimitiveType.Null)); + + TypeName? typeName = s_primitiveTypeNames[(int)primitiveType]; + if (typeName is null) + { + string fullName = primitiveType switch + { + PrimitiveType.Boolean => "System.Boolean", + PrimitiveType.Byte => "System.Byte", + PrimitiveType.SByte => "System.SByte", + PrimitiveType.Char => "System.Char", + PrimitiveType.Int16 => "System.Int16", + PrimitiveType.UInt16 => "System.UInt16", + PrimitiveType.Int32 => "System.Int32", + PrimitiveType.UInt32 => "System.UInt32", + PrimitiveType.Int64 => "System.Int64", + PrimitiveType.UInt64 => "System.UInt64", + PrimitiveType.Single => "System.Single", + PrimitiveType.Double => "System.Double", + PrimitiveType.Decimal => "System.Decimal", + PrimitiveType.TimeSpan => "System.TimeSpan", + PrimitiveType.DateTime => "System.DateTime", + PrimitiveType.String => "System.String", + ObjectPrimitiveType => "System.Object", + IntPtrPrimitiveType => "System.IntPtr", + UIntPtrPrimitiveType => "System.UIntPtr", + _ => throw new ArgumentOutOfRangeException(paramName: nameof(primitiveType), actualValue: primitiveType, message: null) + }; + + s_primitiveTypeNames[(int)primitiveType] = typeName = TypeName.Parse(fullName.AsSpan()).WithCoreLibAssemblyName(); + } + return typeName; + } + + internal static TypeName GetPrimitiveSZArrayTypeName(PrimitiveType primitiveType) + { + TypeName? typeName = s_primitiveSZArrayTypeNames[(int)primitiveType]; + if (typeName is null) + { + s_primitiveSZArrayTypeNames[(int)primitiveType] = typeName = GetPrimitiveTypeName(primitiveType).MakeSZArrayTypeName(); + } + return typeName; + } + + internal static PrimitiveType GetPrimitiveType() + { + if (typeof(T) == typeof(bool)) + return PrimitiveType.Boolean; + else if (typeof(T) == typeof(byte)) + return PrimitiveType.Byte; + else if (typeof(T) == typeof(sbyte)) + return PrimitiveType.SByte; + else if (typeof(T) == typeof(char)) + return PrimitiveType.Char; + else if (typeof(T) == typeof(short)) + return PrimitiveType.Int16; + else if (typeof(T) == typeof(ushort)) + return PrimitiveType.UInt16; + else if (typeof(T) == typeof(int)) + return PrimitiveType.Int32; + else if (typeof(T) == typeof(uint)) + return PrimitiveType.UInt32; + else if (typeof(T) == typeof(long)) + return PrimitiveType.Int64; + else if (typeof(T) == typeof(ulong)) + return PrimitiveType.UInt64; + else if (typeof(T) == typeof(float)) + return PrimitiveType.Single; + else if (typeof(T) == typeof(double)) + return PrimitiveType.Double; + else if (typeof(T) == typeof(decimal)) + return PrimitiveType.Decimal; + else if (typeof(T) == typeof(DateTime)) + return PrimitiveType.DateTime; + else if (typeof(T) == typeof(TimeSpan)) + return PrimitiveType.TimeSpan; + else if (typeof(T) == typeof(string)) + return PrimitiveType.String; + else if (typeof(T) == typeof(IntPtr)) + return IntPtrPrimitiveType; + else if (typeof(T) == typeof(UIntPtr)) + return UIntPtrPrimitiveType; + else + throw new InvalidOperationException(); + } + + internal static TypeName ParseNonSystemClassRecordTypeName(this string rawName, BinaryLibraryRecord libraryRecord, PayloadOptions payloadOptions) + { + if (libraryRecord.LibraryName is not null) + { + return ParseWithoutAssemblyName(rawName, payloadOptions).With(libraryRecord.LibraryName); + } + + Debug.Assert(payloadOptions.UndoTruncatedTypeNames); + Debug.Assert(libraryRecord.RawLibraryName is not null); + + // Combining type and library allows us for handling truncated generic type names that may be present in resources. + ArraySegment assemblyQualifiedName = RentAssemblyQualifiedName(rawName, libraryRecord.RawLibraryName); + TypeName.TryParse(assemblyQualifiedName.AsSpan(), out TypeName? typeName, payloadOptions.TypeNameParseOptions); + ArrayPool.Shared.Return(assemblyQualifiedName.Array!); + + if (typeName is null) + { + throw new SerializationException(SR.Format(SR.Serialization_InvalidTypeOrAssemblyName, rawName, libraryRecord.RawLibraryName)); + } + + if (typeName.AssemblyName is null) + { + // Sample invalid input that could lead us here: + // TypeName: System.Collections.Generic.List`1[[System.String + // LibraryName: 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]] + // Since the flag is ON, we know it's mangling and we provide missing information. + typeName = typeName.WithCoreLibAssemblyName(); + } + + return typeName; + } + + internal static TypeName ParseSystemRecordTypeName(this string rawName, PayloadOptions payloadOptions) + => ParseWithoutAssemblyName(rawName, payloadOptions) + .WithCoreLibAssemblyName(); // We know it's a System Record, so we set the LibraryName to CoreLib + + internal static TypeName WithCoreLibAssemblyName(this TypeName systemType) + => systemType.With(s_coreLibAssemblyName ??= AssemblyNameInfo.Parse("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089".AsSpan())); + + private static TypeName With(this TypeName typeName, AssemblyNameInfo assemblyName) + { + if (!typeName.IsSimple) + { + if (typeName.IsArray) + { + TypeName newElementType = typeName.GetElementType().With(assemblyName); + + return typeName.IsSZArray + ? newElementType.MakeSZArrayTypeName() + : newElementType.MakeArrayTypeName(typeName.GetArrayRank()); + } + else if (typeName.IsConstructedGenericType) + { + TypeName newGenericTypeDefinition = typeName.GetGenericTypeDefinition().With(assemblyName); + + // We don't change the assembly name of generic arguments on purpose. + return newGenericTypeDefinition.MakeGenericTypeName(typeName.GetGenericArguments()); + } + else + { + // BinaryFormatter can not serialize pointers or references. + ThrowHelper.ThrowInvalidTypeName(typeName.FullName); + } + } + + return typeName.WithAssemblyName(assemblyName); + } + + private static TypeName ParseWithoutAssemblyName(string rawName, PayloadOptions payloadOptions) + { + if (!TypeName.TryParse(rawName.AsSpan(), out TypeName? typeName, payloadOptions.TypeNameParseOptions) + || typeName.AssemblyName is not null) // the type and library names should be provided separately + { + throw new SerializationException(SR.Format(SR.Serialization_InvalidTypeName, rawName)); + } + + return typeName; + } + + private static ArraySegment RentAssemblyQualifiedName(string typeName, string libraryName) + { + int length = typeName.Length + 1 + libraryName.Length; + + char[] rented = ArrayPool.Shared.Rent(length); + + typeName.AsSpan().CopyTo(rented); + rented[typeName.Length] = ','; + libraryName.AsSpan().CopyTo(rented.AsSpan(typeName.Length + 1)); + + return new ArraySegment(rented, 0, length); + } +} diff --git a/src/libraries/System.Formats.Nrbf/tests/TypeMatchTests.cs b/src/libraries/System.Formats.Nrbf/tests/TypeMatchTests.cs index 7bf48eab616899..6a981197f82f1c 100644 --- a/src/libraries/System.Formats.Nrbf/tests/TypeMatchTests.cs +++ b/src/libraries/System.Formats.Nrbf/tests/TypeMatchTests.cs @@ -302,6 +302,12 @@ private static void Verify(T input) where T : notnull Assert.True(one.TypeNameMatches(typeof(T))); + Assert.Equal(typeof(T).GetTypeFullNameIncludingTypeForwards(), one.TypeName.FullName); + if (typeof(T) != typeof(TimeSpan)) // TimeSpan is missing type forwards + { + Assert.Equal(typeof(T).GetAssemblyNameIncludingTypeForwards(), one.TypeName.AssemblyName!.FullName); + } + foreach (Type type in PrimitiveTypes) { Assert.Equal(typeof(T) == type, one.TypeNameMatches(type)); diff --git a/src/libraries/System.Reflection.Metadata/ref/System.Reflection.Metadata.cs b/src/libraries/System.Reflection.Metadata/ref/System.Reflection.Metadata.cs index 367636078e24e1..5a36e098782d4c 100644 --- a/src/libraries/System.Reflection.Metadata/ref/System.Reflection.Metadata.cs +++ b/src/libraries/System.Reflection.Metadata/ref/System.Reflection.Metadata.cs @@ -2445,6 +2445,12 @@ internal TypeName() { } public System.Reflection.Metadata.TypeName GetGenericTypeDefinition() { throw null; } public System.Reflection.Metadata.TypeName GetElementType() { throw null; } public int GetNodeCount() { throw null; } + public System.Reflection.Metadata.TypeName MakeSZArrayTypeName() { throw null; } + public System.Reflection.Metadata.TypeName MakeArrayTypeName(int rank) { throw null; } + public System.Reflection.Metadata.TypeName MakeByRefTypeName() { throw null; } + public System.Reflection.Metadata.TypeName MakeGenericTypeName(System.Collections.Immutable.ImmutableArray typeArguments) { throw null; } + public System.Reflection.Metadata.TypeName MakePointerTypeName() { throw null; } + public System.Reflection.Metadata.TypeName WithAssemblyName(AssemblyNameInfo? assemblyName) { throw null; } } public sealed partial class TypeNameParseOptions { diff --git a/src/libraries/System.Reflection.Metadata/src/Resources/Strings.resx b/src/libraries/System.Reflection.Metadata/src/Resources/Strings.resx index 963e4d0af9f8a3..21bab756dd6a89 100644 --- a/src/libraries/System.Reflection.Metadata/src/Resources/Strings.resx +++ b/src/libraries/System.Reflection.Metadata/src/Resources/Strings.resx @@ -432,4 +432,7 @@ The given assembly name was invalid. + + '{0}' is not a simple TypeName. + \ No newline at end of file diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/TypeName.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/TypeName.cs index 3f1ea8394393c8..8920e5a31c39fb 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/TypeName.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/TypeName.cs @@ -25,7 +25,7 @@ sealed class TypeName /// Positive value is array rank. /// Negative value is modifier encoded using constants defined in . /// - private readonly sbyte _rankOrModifier; + private readonly int _rankOrModifier; /// /// To avoid the need of allocating a string for all declaring types (example: A+B+C+D+E+F+G), /// length of the name is stored and the fullName passed in ctor represents the full name of the nested type. @@ -50,7 +50,7 @@ internal TypeName(string? fullName, #else ImmutableArray.Builder? genericTypeArguments = default, #endif - sbyte rankOrModifier = default, + int rankOrModifier = default, int nestedNameLength = -1) { _fullName = fullName; @@ -69,6 +69,25 @@ internal TypeName(string? fullName, #endif } +#if SYSTEM_REFLECTION_METADATA + private TypeName(string? fullName, + AssemblyNameInfo? assemblyName, + TypeName? elementOrGenericType, + TypeName? declaringType, + ImmutableArray genericTypeArguments, + int rankOrModifier = default, + int nestedNameLength = -1) + { + _fullName = fullName; + AssemblyName = assemblyName; + _elementOrGenericType = elementOrGenericType; + _declaringType = declaringType; + _genericArguments = genericTypeArguments; + _rankOrModifier = rankOrModifier; + _nestedNameLength = nestedNameLength; + } +#endif + /// /// The assembly-qualified name of the type; e.g., "System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089". /// @@ -392,5 +411,101 @@ public int GetArrayRank() #else ImmutableArray GetGenericArguments() => _genericArguments; #endif + +#if SYSTEM_REFLECTION_METADATA + /// + /// Creates a new object that represents current simple name with provided assembly name. + /// + /// Assembly name. + /// Created simple name. + /// The current type name is not simple. + public TypeName WithAssemblyName(AssemblyNameInfo? assemblyName) + { + if (!IsSimple) + { + TypeNameParserHelpers.ThrowInvalidOperation_NotSimpleName(FullName); + } + + TypeName? declaringType = IsNested + ? DeclaringType.WithAssemblyName(assemblyName) + : null; + + return new TypeName(fullName: _fullName, + assemblyName: assemblyName, + elementOrGenericType: null, + declaringType: declaringType, + genericTypeArguments: ImmutableArray.Empty, + nestedNameLength: _nestedNameLength); + } + + /// + /// Creates a object representing a one-dimensional array + /// of the current type, with a lower bound of zero. + /// + /// + /// A object representing a one-dimensional array + /// of the current type, with a lower bound of zero. + /// + public TypeName MakeSZArrayTypeName() => MakeElementTypeName(TypeNameParserHelpers.SZArray); + + /// + /// Creates a object representing an array of the current type, + /// with the specified number of dimensions. + /// + /// The number of dimensions for the array. This number must be more than zero and less than or equal to 32. + /// + /// A object representing an array of the current type, + /// with the specified number of dimensions. + /// + /// rank is invalid. For example, 0 or negative. + public TypeName MakeArrayTypeName(int rank) + => rank <= 0 + ? throw new ArgumentOutOfRangeException(nameof(rank)) + : MakeElementTypeName(rank); + + /// + /// Creates a object that represents a pointer to the current type. + /// + /// + /// A object that represents a pointer to the current type. + /// + public TypeName MakePointerTypeName() => MakeElementTypeName(TypeNameParserHelpers.Pointer); + + /// + /// Creates a object that represents a managed reference to the current type. + /// + /// + /// A object that represents a managed reference to the current type. + /// + public TypeName MakeByRefTypeName() => MakeElementTypeName(TypeNameParserHelpers.ByRef); + + /// + /// Creates a new constructed generic type name. + /// + /// An array of type names to be used as generic arguments of the current simple type name. + /// + /// A representing the constructed type name formed by using the elements + /// of for the generic arguments of the current simple type name. + /// + /// The current type name is not simple. + public TypeName MakeGenericTypeName(ImmutableArray typeArguments) + { + if (!IsSimple) + { + TypeNameParserHelpers.ThrowInvalidOperation_NotSimpleName(FullName); + } + + return new TypeName(fullName: null, AssemblyName, elementOrGenericType: this, declaringType: _declaringType, genericTypeArguments: typeArguments); + } + + private TypeName MakeElementTypeName(int rankOrModifier) + => new TypeName( + fullName: null, + assemblyName: AssemblyName, + elementOrGenericType: this, + declaringType: null, + genericTypeArguments: ImmutableArray.Empty, + rankOrModifier: rankOrModifier); +#endif } } diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/TypeNameParserHelpers.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/TypeNameParserHelpers.cs index 2fa33ea1dfe3bf..ef0a9e702a1a22 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/TypeNameParserHelpers.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/TypeNameParserHelpers.cs @@ -388,5 +388,13 @@ internal static void ThrowInvalidOperation_HasToBeArrayClass() throw new InvalidOperationException(); #endif } + +#if SYSTEM_REFLECTION_METADATA + [DoesNotReturn] + internal static void ThrowInvalidOperation_NotSimpleName(string fullName) + { + throw new InvalidOperationException(SR.Format(SR.Arg_NotSimpleTypeName, fullName)); + } +#endif } } diff --git a/src/libraries/System.Reflection.Metadata/tests/Metadata/TypeNameTests.cs b/src/libraries/System.Reflection.Metadata/tests/Metadata/TypeNameTests.cs index a5e22b5d6c503b..1fbc9f4e011d17 100644 --- a/src/libraries/System.Reflection.Metadata/tests/Metadata/TypeNameTests.cs +++ b/src/libraries/System.Reflection.Metadata/tests/Metadata/TypeNameTests.cs @@ -107,9 +107,7 @@ public void TypeNameCanContainAssemblyName(Type type) static void Verify(Type type, AssemblyName expectedAssemblyName, TypeName parsed) { - Assert.Equal(type.AssemblyQualifiedName, parsed.AssemblyQualifiedName); - Assert.Equal(type.FullName, parsed.FullName); - Assert.Equal(type.Name, parsed.Name); + EnsureBasicMatch(parsed, type); AssemblyNameInfo parsedAssemblyName = parsed.AssemblyName; Assert.NotNull(parsedAssemblyName); @@ -484,9 +482,127 @@ public void GetNodeCountReturnsExpectedValue(Type type, int expected) Assert.Equal(expected, parsed.GetNodeCount()); - Assert.Equal(type.Name, parsed.Name); - Assert.Equal(type.FullName, parsed.FullName); - Assert.Equal(type.AssemblyQualifiedName, parsed.AssemblyQualifiedName); + EnsureBasicMatch(parsed, type); + + if (!type.IsByRef) + { + EnsureBasicMatch(parsed.MakeSZArrayTypeName(), type.MakeArrayType()); + EnsureBasicMatch(parsed.MakeArrayTypeName(3), type.MakeArrayType(3)); + EnsureBasicMatch(parsed.MakeByRefTypeName(), type.MakeByRefType()); + EnsureBasicMatch(parsed.MakePointerTypeName(), type.MakePointerType()); + } + } + + [Fact] + public void MakeGenericTypeName() + { + Type genericTypeDefinition = typeof(Dictionary<,>); + TypeName genericTypeNameDefinition = TypeName.Parse(genericTypeDefinition.AssemblyQualifiedName.AsSpan()); + + Type[] genericArgs = new Type[] { typeof(int), typeof(bool) }; + ImmutableArray genericTypeNames = genericArgs.Select(type => TypeName.Parse(type.AssemblyQualifiedName.AsSpan())).ToImmutableArray(); + + TypeName genericTypeName = genericTypeNameDefinition.MakeGenericTypeName(genericTypeNames); + Type genericType = genericTypeDefinition.MakeGenericType(genericArgs); + + EnsureBasicMatch(genericTypeName, genericType); + + TypeName szArrayTypeName = genericTypeNameDefinition.MakeSZArrayTypeName(); + Assert.Throws(() => szArrayTypeName.MakeGenericTypeName(genericTypeNames)); + TypeName mdArrayTypeName = genericTypeNameDefinition.MakeArrayTypeName(3); + Assert.Throws(() => mdArrayTypeName.MakeGenericTypeName(genericTypeNames)); + TypeName pointerTypeName = genericTypeNameDefinition.MakePointerTypeName(); + Assert.Throws(() => pointerTypeName.MakeGenericTypeName(genericTypeNames)); + TypeName byRefTypeName = genericTypeNameDefinition.MakeByRefTypeName(); + Assert.Throws(() => byRefTypeName.MakeGenericTypeName(genericTypeNames)); + } + + [Theory] + [InlineData("Simple")] + [InlineData("Pointer*")] + [InlineData("Pointer*******")] + [InlineData("ByRef&")] + [InlineData("SZArray[]")] + [InlineData("CustomOffsetArray[*]")] + [InlineData("MDArray[,]")] + [InlineData("Declaring+Nested")] + [InlineData("Declaring+Deep+Deeper+Nested")] + [InlineData("Generic[[GenericArg]]")] + public void WithAssemblyNameThrowsForNonSimpleNames(string input) + { + TypeName typeName = TypeName.Parse(input.AsSpan()); + + if (typeName.IsSimple) + { + AssemblyNameInfo assemblyName = new("MyName"); + TypeName simple = typeName.WithAssemblyName(assemblyName: assemblyName); + + Assert.Equal(typeName.FullName, simple.FullName); // full name has not changed + Assert.NotEqual(typeName.AssemblyQualifiedName, simple.AssemblyQualifiedName); + Assert.Equal($"{typeName.FullName}, {assemblyName.FullName}", simple.AssemblyQualifiedName); + + Assert.True(simple.IsSimple); + Assert.False(simple.IsArray); + Assert.False(simple.IsConstructedGenericType); + Assert.False(simple.IsPointer); + Assert.False(simple.IsByRef); + Assert.Equal(typeName.IsNested, simple.IsNested); + } + else + { + Assert.Throws(() => typeName.WithAssemblyName(assemblyName: null)); + } + } + + [Theory] + [InlineData("Declaring+Nested")] + [InlineData("Declaring+Nested, Lib")] + [InlineData("Declaring+Deep+Deeper+Nested")] + [InlineData("Declaring+Deep+Deeper+Nested, Lib")] + public void WithAssemblyName_NestedNames(string name) + { + AssemblyNameInfo assemblyName = new("New"); + TypeName parsed = TypeName.Parse(name.AsSpan()); + TypeName made = parsed.WithAssemblyName(assemblyName); + + VerifyNestedNames(parsed, made, assemblyName); + } + + [Theory] + [InlineData(typeof(NestedNonGeneric_0.NestedGeneric_1))] + [InlineData(typeof(NestedGeneric_0))] + [InlineData(typeof(NestedGeneric_0.NestedNonGeneric_1))] + [InlineData(typeof(NestedGeneric_0.NestedGeneric_1))] + [InlineData(typeof(NestedGeneric_0.NestedGeneric_1.NestedNonGeneric_2))] + [InlineData(typeof(NestedGeneric_0.NestedGeneric_1.NestedGeneric_2))] + [InlineData(typeof(NestedGeneric_0.NestedGeneric_1.NestedGeneric_2.NestedNonGeneric_3))] + public void MakeGenericTypeName_NestedNames(Type type) + { + AssemblyNameInfo assemblyName = new("New"); + TypeName parsed = TypeName.Parse(type.AssemblyQualifiedName.AsSpan()); + TypeName made = parsed.GetGenericTypeDefinition().WithAssemblyName(assemblyName).MakeGenericTypeName(parsed.GetGenericArguments()); + + VerifyNestedNames(parsed, made, assemblyName); + } + + private static void VerifyNestedNames(TypeName parsed, TypeName made, AssemblyNameInfo assemblyName) + { + while (true) + { + Assert.Equal(parsed.Name, made.Name); + Assert.Equal(parsed.FullName, made.FullName); + Assert.Equal(assemblyName, made.AssemblyName); + Assert.NotEqual(parsed.AssemblyQualifiedName, made.AssemblyQualifiedName); + Assert.EndsWith(assemblyName.FullName, made.AssemblyQualifiedName); + + if (!parsed.IsNested) + { + break; + } + + parsed = parsed.DeclaringType; + made = made.DeclaringType; + } } [Fact] @@ -561,9 +677,7 @@ public void ParsedNamesMatchSystemTypeNames(Type type) { TypeName parsed = TypeName.Parse(type.AssemblyQualifiedName.AsSpan()); - Assert.Equal(type.Name, parsed.Name); - Assert.Equal(type.FullName, parsed.FullName); - Assert.Equal(type.AssemblyQualifiedName, parsed.AssemblyQualifiedName); + EnsureBasicMatch(parsed, type); if (type.IsGenericType) { @@ -626,9 +740,7 @@ static void Test(Type type) TypeName parsed = TypeName.Parse(type.AssemblyQualifiedName.AsSpan()); // ensure that Name, FullName and AssemblyQualifiedName match reflection APIs!! - Assert.Equal(type.Name, parsed.Name); - Assert.Equal(type.FullName, parsed.FullName); - Assert.Equal(type.AssemblyQualifiedName, parsed.AssemblyQualifiedName); + EnsureBasicMatch(parsed, type); // now load load the type from name Verify(type, parsed, ignoreCase: false); #if NET // something weird is going on here @@ -729,9 +841,27 @@ static void Verify(Type type, TypeName typeName, bool ignoreCase) #pragma warning restore IL2055, IL2057, IL2075, IL2096 } + private static void EnsureBasicMatch(TypeName typeName, Type type) + { + Assert.Equal(type.AssemblyQualifiedName, typeName.AssemblyQualifiedName); + Assert.Equal(type.FullName, typeName.FullName); + Assert.Equal(type.Name, typeName.Name); + +#if NET + Assert.Equal(type.IsSZArray, typeName.IsSZArray); +#endif + Assert.Equal(type.IsArray, typeName.IsArray); + Assert.Equal(type.IsPointer, typeName.IsPointer); + Assert.Equal(type.IsByRef, typeName.IsByRef); + Assert.Equal(type.IsConstructedGenericType, typeName.IsConstructedGenericType); + Assert.Equal(type.IsNested, typeName.IsNested); + } + public class NestedNonGeneric_0 { public class NestedNonGeneric_1 { } + + public class NestedGeneric_1 { } } public class NestedGeneric_0 @@ -742,7 +872,10 @@ public class NestedGeneric_2 { public class NestedNonGeneric_3 { } } + + public class NestedNonGeneric_2 { } } + public class NestedNonGeneric_1 { } } } }