| | | 1 | | // Licensed to the .NET Foundation under one or more agreements. |
| | | 2 | | // The .NET Foundation licenses this file to you under the MIT license. |
| | | 3 | | |
| | | 4 | | using System.Collections.Generic; |
| | | 5 | | using System.Diagnostics; |
| | | 6 | | using System.Diagnostics.CodeAnalysis; |
| | | 7 | | using System.Runtime.InteropServices; |
| | | 8 | | using System.Text; |
| | | 9 | | |
| | | 10 | | #if !SYSTEM_PRIVATE_CORELIB |
| | | 11 | | using System.Collections.Immutable; |
| | | 12 | | #endif |
| | | 13 | | |
| | | 14 | | namespace System.Reflection.Metadata |
| | | 15 | | { |
| | | 16 | | [DebuggerDisplay("{AssemblyQualifiedName}")] |
| | | 17 | | #if SYSTEM_REFLECTION_METADATA |
| | | 18 | | public |
| | | 19 | | #else |
| | | 20 | | internal |
| | | 21 | | #endif |
| | | 22 | | sealed class TypeName |
| | | 23 | | { |
| | | 24 | | /// <summary> |
| | | 25 | | /// Positive value is array rank. |
| | | 26 | | /// Negative value is modifier encoded using constants defined in <see cref="TypeNameParserHelpers"/>. |
| | | 27 | | /// </summary> |
| | | 28 | | private readonly int _rankOrModifier; |
| | | 29 | | /// <summary> |
| | | 30 | | /// To avoid the need of allocating a string for all declaring types (example: A+B+C+D+E+F+G), |
| | | 31 | | /// length of the name is stored and the fullName passed in ctor represents the full name of the nested type. |
| | | 32 | | /// So when the name is needed, a substring is being performed. |
| | | 33 | | /// </summary> |
| | | 34 | | private readonly int _nestedNameLength; |
| | | 35 | | private readonly TypeName? _elementOrGenericType; |
| | | 36 | | private readonly TypeName? _declaringType; |
| | | 37 | | #if SYSTEM_PRIVATE_CORELIB |
| | | 38 | | private readonly List<TypeName>? _genericArguments; |
| | | 39 | | #else |
| | | 40 | | private readonly ImmutableArray<TypeName> _genericArguments; |
| | | 41 | | #endif |
| | | 42 | | private string? _name, _namespace, _fullName, _assemblyQualifiedName; |
| | | 43 | | |
| | 64132 | 44 | | internal TypeName(string? fullName, |
| | 64132 | 45 | | AssemblyNameInfo? assemblyName, |
| | 64132 | 46 | | TypeName? elementOrGenericType = default, |
| | 64132 | 47 | | TypeName? declaringType = default, |
| | 64132 | 48 | | #if SYSTEM_PRIVATE_CORELIB |
| | 64132 | 49 | | List<TypeName>? genericTypeArguments = default, |
| | 64132 | 50 | | #else |
| | 64132 | 51 | | ImmutableArray<TypeName>.Builder? genericTypeArguments = default, |
| | 64132 | 52 | | #endif |
| | 64132 | 53 | | int rankOrModifier = default, |
| | 64132 | 54 | | int nestedNameLength = -1) |
| | 64132 | 55 | | { |
| | 64132 | 56 | | _fullName = fullName; |
| | 64132 | 57 | | AssemblyName = assemblyName; |
| | 64132 | 58 | | _rankOrModifier = rankOrModifier; |
| | 64132 | 59 | | _elementOrGenericType = elementOrGenericType; |
| | 64132 | 60 | | _declaringType = declaringType; |
| | 64132 | 61 | | _nestedNameLength = nestedNameLength; |
| | | 62 | | |
| | | 63 | | #if SYSTEM_PRIVATE_CORELIB |
| | | 64 | | _genericArguments = genericTypeArguments; |
| | | 65 | | #else |
| | 64132 | 66 | | _genericArguments = genericTypeArguments is null |
| | 64132 | 67 | | ? ImmutableArray<TypeName>.Empty |
| | 64132 | 68 | | : genericTypeArguments.Count == genericTypeArguments.Capacity ? genericTypeArguments.MoveToImmutable() : |
| | | 69 | | #endif |
| | 64132 | 70 | | } |
| | | 71 | | |
| | | 72 | | #if SYSTEM_REFLECTION_METADATA |
| | 0 | 73 | | private TypeName(string? fullName, |
| | 0 | 74 | | AssemblyNameInfo? assemblyName, |
| | 0 | 75 | | TypeName? elementOrGenericType, |
| | 0 | 76 | | TypeName? declaringType, |
| | 0 | 77 | | ImmutableArray<TypeName> genericTypeArguments, |
| | 0 | 78 | | int rankOrModifier = default, |
| | 0 | 79 | | int nestedNameLength = -1) |
| | 0 | 80 | | { |
| | 0 | 81 | | _fullName = fullName; |
| | 0 | 82 | | AssemblyName = assemblyName; |
| | 0 | 83 | | _elementOrGenericType = elementOrGenericType; |
| | 0 | 84 | | _declaringType = declaringType; |
| | 0 | 85 | | _genericArguments = genericTypeArguments; |
| | 0 | 86 | | _rankOrModifier = rankOrModifier; |
| | 0 | 87 | | _nestedNameLength = nestedNameLength; |
| | 0 | 88 | | } |
| | | 89 | | #endif |
| | | 90 | | |
| | | 91 | | /// <summary> |
| | | 92 | | /// The assembly-qualified name of the type; e.g., "System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, Pu |
| | | 93 | | /// </summary> |
| | | 94 | | /// <remarks> |
| | | 95 | | /// If <see cref="AssemblyName"/> returns null, simply returns <see cref="FullName"/>. |
| | | 96 | | /// </remarks> |
| | | 97 | | public string AssemblyQualifiedName |
| | | 98 | | { |
| | | 99 | | get |
| | 7344 | 100 | | { |
| | 7344 | 101 | | if (_assemblyQualifiedName is null) |
| | 7344 | 102 | | { |
| | 7344 | 103 | | if (_fullName is not null && AssemblyName is null) |
| | 3472 | 104 | | { |
| | | 105 | | // _fullName may carry more information than FullName property, so we need to use FullName prope |
| | 3472 | 106 | | _assemblyQualifiedName = FullName; |
| | 3472 | 107 | | } |
| | | 108 | | else |
| | 3872 | 109 | | { |
| | 3872 | 110 | | ValueStringBuilder builder = new(stackalloc char[256]); |
| | 3872 | 111 | | AppendFullName(ref builder); // see recursion comments in AppendFullName |
| | 3872 | 112 | | if (AssemblyName is not null) |
| | 3872 | 113 | | { |
| | 3872 | 114 | | builder.Append(", "); |
| | 3872 | 115 | | AssemblyName.AppendFullName(ref builder); |
| | 3872 | 116 | | } |
| | 3872 | 117 | | _assemblyQualifiedName = builder.ToString(); |
| | | 118 | | |
| | 3872 | 119 | | if (AssemblyName is null) |
| | 0 | 120 | | { |
| | | 121 | | // If the type name was not created from a assembly-qualified name, |
| | | 122 | | // the FullName and AssemblyQualifiedName are the same. |
| | 0 | 123 | | _fullName = _assemblyQualifiedName; |
| | 0 | 124 | | } |
| | 3872 | 125 | | } |
| | 7344 | 126 | | } |
| | | 127 | | |
| | 7344 | 128 | | return _assemblyQualifiedName; |
| | 7344 | 129 | | } |
| | | 130 | | } |
| | | 131 | | |
| | | 132 | | /// <summary> |
| | | 133 | | /// Returns assembly name which contains this type, or null if this <see cref="TypeName"/> was not |
| | | 134 | | /// created from a fully-qualified name. |
| | | 135 | | /// </summary> |
| | 56988 | 136 | | public AssemblyNameInfo? AssemblyName { get; } |
| | | 137 | | |
| | | 138 | | /// <summary> |
| | | 139 | | /// If this type is a nested type (see <see cref="IsNested"/>), gets |
| | | 140 | | /// the declaring type. If this type is not a nested type, throws. |
| | | 141 | | /// </summary> |
| | | 142 | | /// <remarks> |
| | | 143 | | /// For example, given "Namespace.Declaring+Nested", unwraps the outermost type and returns "Namespace.Declaring |
| | | 144 | | /// </remarks> |
| | | 145 | | /// <exception cref="InvalidOperationException">The current type is not a nested type.</exception> |
| | | 146 | | public TypeName DeclaringType |
| | | 147 | | { |
| | | 148 | | get |
| | 2576 | 149 | | { |
| | 2576 | 150 | | if (_declaringType is null) |
| | 0 | 151 | | { |
| | 0 | 152 | | TypeNameParserHelpers.ThrowInvalidOperation_NotNestedType(); |
| | | 153 | | } |
| | | 154 | | |
| | 2576 | 155 | | return _declaringType; |
| | 2576 | 156 | | } |
| | | 157 | | } |
| | | 158 | | |
| | | 159 | | /// <summary> |
| | | 160 | | /// The full name of this type, including namespace, but without the assembly name; e.g., "System.Int32". |
| | | 161 | | /// Nested types are represented with a '+'; e.g., "MyNamespace.MyType+NestedType". |
| | | 162 | | /// </summary> |
| | | 163 | | /// <remarks> |
| | | 164 | | /// <para>For constructed generic types, the type arguments will be listed using their fully qualified |
| | | 165 | | /// names. For example, given "List<int>", the <see cref="FullName"/> property will return |
| | | 166 | | /// "System.Collections.Generic.List`1[[System.Int32, mscorlib, ...]]".</para> |
| | | 167 | | /// <para>For open generic types, the convention is to use a backtick ("`") followed by |
| | | 168 | | /// the arity of the generic type. For example, given "Dictionary<,>", the <see cref="FullName"/> |
| | | 169 | | /// property will return "System.Collections.Generic.Dictionary`2". Given "Dictionary<,>.Enumerator", |
| | | 170 | | /// the <see cref="FullName"/> property will return "System.Collections.Generic.Dictionary`2+Enumerator". |
| | | 171 | | /// See ECMA-335, Sec. I.10.7.2 (Type names and arity encoding) for more information.</para> |
| | | 172 | | /// </remarks> |
| | | 173 | | public string FullName |
| | | 174 | | { |
| | | 175 | | get |
| | 10816 | 176 | | { |
| | 10816 | 177 | | if (_fullName is null) |
| | 3548 | 178 | | { |
| | 3548 | 179 | | ValueStringBuilder builder = new(stackalloc char[128]); |
| | 3548 | 180 | | AppendFullName(ref builder); |
| | 3548 | 181 | | _fullName = builder.ToString(); |
| | 3548 | 182 | | } |
| | 7268 | 183 | | else if (_nestedNameLength > 0 && _fullName.Length > _nestedNameLength) // Declaring types |
| | 0 | 184 | | { |
| | | 185 | | // Stored fullName represents the full name of the nested type. |
| | | 186 | | // Example: Namespace.Declaring+Nested |
| | 0 | 187 | | _fullName = _fullName.Substring(0, _nestedNameLength); |
| | 0 | 188 | | } |
| | | 189 | | |
| | 10816 | 190 | | return _fullName!; |
| | 10816 | 191 | | } |
| | | 192 | | } |
| | | 193 | | |
| | | 194 | | private void AppendFullName(ref ValueStringBuilder builder) |
| | 32864 | 195 | | { |
| | | 196 | | // This is a recursive method over potentially hostile input. Protection against DoS is offered |
| | | 197 | | // via the [Try]Parse method and TypeNameParseOptions.MaxNodes property at construction time. |
| | | 198 | | // This FullName property getter and related methods assume that this TypeName instance has an |
| | | 199 | | // acceptable node count. |
| | | 200 | | // |
| | | 201 | | // The node count controls the total amount of work performed by this method, including: |
| | | 202 | | // - The max possible stack depth due to the recursive methods calls. |
| | | 203 | | |
| | 32864 | 204 | | if (_fullName is null) |
| | 12360 | 205 | | { |
| | 12360 | 206 | | if (IsConstructedGenericType) |
| | 7656 | 207 | | { |
| | 7656 | 208 | | GetGenericTypeDefinition().AppendFullName(ref builder); |
| | 7656 | 209 | | builder.Append('['); |
| | 49136 | 210 | | foreach (TypeName genericArg in GetGenericArguments()) |
| | 13084 | 211 | | { |
| | 13084 | 212 | | builder.Append('['); |
| | 13084 | 213 | | genericArg.AppendFullName(ref builder); |
| | | 214 | | // Generic arguments need to be always fully qualified. |
| | 13084 | 215 | | if (genericArg.AssemblyName is not null) |
| | 2312 | 216 | | { |
| | 2312 | 217 | | builder.Append(", "); |
| | 2312 | 218 | | genericArg.AssemblyName.AppendFullName(ref builder); |
| | 2312 | 219 | | } |
| | 13084 | 220 | | builder.Append("],"); |
| | 13084 | 221 | | } |
| | 7656 | 222 | | builder[builder.Length - 1] = ']'; // replace ',' with ']' |
| | 7656 | 223 | | } |
| | 4704 | 224 | | else if (IsArray || IsPointer || IsByRef) |
| | 4704 | 225 | | { |
| | 4704 | 226 | | GetElementType().AppendFullName(ref builder); |
| | 4704 | 227 | | TypeNameParserHelpers.AppendRankOrModifierStringRepresentation(_rankOrModifier, ref builder); |
| | 4704 | 228 | | } |
| | | 229 | | else |
| | 0 | 230 | | { |
| | 0 | 231 | | Debug.Fail("Pre-allocated full name should have been provided in the ctor"); |
| | | 232 | | } |
| | 12360 | 233 | | } |
| | 20504 | 234 | | else if (_nestedNameLength > 0 && _fullName.Length > _nestedNameLength) // Declaring types |
| | 0 | 235 | | { |
| | | 236 | | // Stored fullName represents the full name of the nested type. |
| | | 237 | | // Example: Namespace.Declaring+Nested |
| | 0 | 238 | | builder.Append(_fullName.AsSpan(0, _nestedNameLength)); |
| | 0 | 239 | | } |
| | | 240 | | else |
| | 20504 | 241 | | { |
| | 20504 | 242 | | builder.Append(_fullName); |
| | 20504 | 243 | | } |
| | 32864 | 244 | | } |
| | | 245 | | |
| | | 246 | | /// <summary> |
| | | 247 | | /// Returns true if this type represents any kind of array, regardless of the array's |
| | | 248 | | /// rank or its bounds. |
| | | 249 | | /// </summary> |
| | 108276 | 250 | | public bool IsArray => _rankOrModifier == TypeNameParserHelpers.SZArray || _rankOrModifier > 0; |
| | | 251 | | |
| | | 252 | | /// <summary> |
| | | 253 | | /// Returns true if this type represents a constructed generic type (e.g., "List<int>"). |
| | | 254 | | /// </summary> |
| | | 255 | | /// <remarks> |
| | | 256 | | /// Returns false for open generic types (e.g., "Dictionary<,>"). |
| | | 257 | | /// </remarks> |
| | | 258 | | public bool IsConstructedGenericType => |
| | | 259 | | #if SYSTEM_PRIVATE_CORELIB |
| | | 260 | | _genericArguments is not null; |
| | | 261 | | #else |
| | 111096 | 262 | | _genericArguments.Length > 0; |
| | | 263 | | #endif |
| | | 264 | | |
| | | 265 | | /// <summary> |
| | | 266 | | /// Returns true if this is a "plain" type; that is, not an array, not a pointer, not a reference, and |
| | | 267 | | /// not a constructed generic type. Examples of elemental types are "System.Int32", |
| | | 268 | | /// "System.Uri", and "YourNamespace.YourClass". |
| | | 269 | | /// </summary> |
| | | 270 | | /// <remarks> |
| | | 271 | | /// <para>This property returning true doesn't mean that the type is a primitive like string |
| | | 272 | | /// or int; it just means that there's no underlying type.</para> |
| | | 273 | | /// <para>This property will return true for generic type definitions (e.g., "Dictionary<,>"). |
| | | 274 | | /// This is because determining whether a type truly is a generic type requires loading the type |
| | | 275 | | /// and performing a runtime check.</para> |
| | | 276 | | /// </remarks> |
| | | 277 | | [MemberNotNullWhen(false, nameof(_elementOrGenericType))] |
| | 7344 | 278 | | public bool IsSimple => _elementOrGenericType is null; |
| | | 279 | | |
| | | 280 | | /// <summary> |
| | | 281 | | /// Returns true if this is a managed pointer type (e.g., "ref int"). |
| | | 282 | | /// Managed pointer types are sometimes called byref types (<seealso cref="Type.IsByRef"/>) |
| | | 283 | | /// </summary> |
| | 72948 | 284 | | public bool IsByRef => _rankOrModifier == TypeNameParserHelpers.ByRef; |
| | | 285 | | |
| | | 286 | | /// <summary> |
| | | 287 | | /// Returns true if this is a nested type (e.g., "Namespace.Declaring+Nested"). |
| | | 288 | | /// For nested types <seealso cref="DeclaringType"/> returns their declaring type. |
| | | 289 | | /// </summary> |
| | | 290 | | [MemberNotNullWhen(true, nameof(_declaringType))] |
| | 58120 | 291 | | public bool IsNested => _declaringType is not null; |
| | | 292 | | |
| | | 293 | | /// <summary> |
| | | 294 | | /// Returns true if this type represents a single-dimensional, zero-indexed array (e.g., "int[]"). |
| | | 295 | | /// </summary> |
| | 0 | 296 | | public bool IsSZArray => _rankOrModifier == TypeNameParserHelpers.SZArray; |
| | | 297 | | |
| | | 298 | | /// <summary> |
| | | 299 | | /// Returns true if this type represents an unmanaged pointer (e.g., "int*" or "void*"). |
| | | 300 | | /// Unmanaged pointer types are often just called pointers (<seealso cref="Type.IsPointer"/>) |
| | | 301 | | /// </summary> |
| | 85532 | 302 | | public bool IsPointer => _rankOrModifier == TypeNameParserHelpers.Pointer; |
| | | 303 | | |
| | | 304 | | /// <summary> |
| | | 305 | | /// Returns true if this type represents a variable-bound array; that is, an array of rank greater |
| | | 306 | | /// than 1 (e.g., "int[,]") or a single-dimensional array which isn't necessarily zero-indexed. |
| | | 307 | | /// </summary> |
| | 0 | 308 | | public bool IsVariableBoundArrayType => _rankOrModifier >= 1; |
| | | 309 | | |
| | | 310 | | /// <summary> |
| | | 311 | | /// The name of this type, without the namespace and the assembly name; e.g., "Int32". |
| | | 312 | | /// Nested types are represented without a '+'; e.g., "MyNamespace.MyType+NestedType" is just "NestedType". |
| | | 313 | | /// </summary> |
| | | 314 | | public string Name |
| | | 315 | | { |
| | | 316 | | get |
| | 7344 | 317 | | { |
| | 7344 | 318 | | if (_name is null) |
| | 7344 | 319 | | { |
| | 7344 | 320 | | ValueStringBuilder builder = new(stackalloc char[64]); |
| | 7344 | 321 | | AppendName(ref builder); |
| | 7344 | 322 | | _name = builder.ToString(); |
| | 7344 | 323 | | } |
| | | 324 | | |
| | 7344 | 325 | | return _name; |
| | 7344 | 326 | | } |
| | | 327 | | } |
| | | 328 | | |
| | | 329 | | private void AppendName(ref ValueStringBuilder builder) |
| | 14344 | 330 | | { |
| | | 331 | | // Lookups to Name and FullName might be recursive. See comments in AppendFullName method. |
| | | 332 | | |
| | 14344 | 333 | | if (IsConstructedGenericType) |
| | 2680 | 334 | | { |
| | 2680 | 335 | | GetGenericTypeDefinition().AppendName(ref builder); |
| | 2680 | 336 | | } |
| | 11664 | 337 | | else if (IsPointer || IsByRef || IsArray) |
| | 4320 | 338 | | { |
| | 4320 | 339 | | GetElementType().AppendName(ref builder); |
| | 4320 | 340 | | TypeNameParserHelpers.AppendRankOrModifierStringRepresentation(_rankOrModifier, ref builder); |
| | 4320 | 341 | | } |
| | | 342 | | else |
| | 7344 | 343 | | { |
| | | 344 | | // _fullName can be null only in constructed generic or modified types, which we handled above. |
| | 7344 | 345 | | Debug.Assert(_fullName is not null); |
| | 7344 | 346 | | ReadOnlySpan<char> name = _fullName.AsSpan(); |
| | 7344 | 347 | | if (_nestedNameLength > 0) |
| | 0 | 348 | | { |
| | 0 | 349 | | name = name.Slice(0, _nestedNameLength); |
| | 0 | 350 | | } |
| | 7344 | 351 | | if (IsNested) |
| | 164 | 352 | | { |
| | | 353 | | // If the type is nested, we know the length of the declaring type's full name. |
| | | 354 | | // Get the characters after that plus one for the '+' separator. |
| | 164 | 355 | | name = name.Slice(_declaringType._nestedNameLength + 1); |
| | 164 | 356 | | } |
| | 7180 | 357 | | else if (TypeNameParserHelpers.IndexOfNamespaceDelimiter(name) is int idx && idx >= 0) |
| | 296 | 358 | | { |
| | | 359 | | // If the type is not nested, find the namespace delimiter in the full name and return the substring |
| | 296 | 360 | | name = name.Slice(idx + 1); |
| | 296 | 361 | | } |
| | 7344 | 362 | | builder.Append(name); |
| | 7344 | 363 | | } |
| | 14344 | 364 | | } |
| | | 365 | | |
| | | 366 | | /// <summary> |
| | | 367 | | /// The namespace of this type; e.g., "System". |
| | | 368 | | /// </summary> |
| | | 369 | | /// <exception cref="InvalidOperationException">This instance is a nested type.</exception> |
| | | 370 | | public string Namespace |
| | | 371 | | { |
| | | 372 | | get |
| | 0 | 373 | | { |
| | 0 | 374 | | if (_namespace is null) |
| | 0 | 375 | | { |
| | 0 | 376 | | TypeName rootTypeName = this; |
| | 0 | 377 | | while (!rootTypeName.IsSimple) |
| | 0 | 378 | | { |
| | 0 | 379 | | rootTypeName = rootTypeName._elementOrGenericType; |
| | 0 | 380 | | } |
| | | 381 | | |
| | 0 | 382 | | if (rootTypeName.IsNested) |
| | 0 | 383 | | { |
| | 0 | 384 | | TypeNameParserHelpers.ThrowInvalidOperation_NestedTypeNamespace(); |
| | | 385 | | } |
| | | 386 | | |
| | | 387 | | // By setting the namespace field at the root type name, we avoid recomputing it for all derived nam |
| | 0 | 388 | | if (rootTypeName._namespace is null) |
| | 0 | 389 | | { |
| | | 390 | | // At this point the type does not have a modifier applied to it, so it should have its full nam |
| | 0 | 391 | | Debug.Assert(rootTypeName._fullName is not null); |
| | 0 | 392 | | ReadOnlySpan<char> rootFullName = rootTypeName._fullName.AsSpan(); |
| | 0 | 393 | | if (rootTypeName._nestedNameLength > 0) |
| | 0 | 394 | | { |
| | 0 | 395 | | rootFullName = rootFullName.Slice(0, rootTypeName._nestedNameLength); |
| | 0 | 396 | | } |
| | 0 | 397 | | if (TypeNameParserHelpers.IndexOfNamespaceDelimiter(rootFullName) is int idx && idx >= 0) |
| | 0 | 398 | | { |
| | 0 | 399 | | rootTypeName._namespace = rootFullName.Slice(0, idx).ToString(); |
| | 0 | 400 | | } |
| | | 401 | | else |
| | 0 | 402 | | { |
| | 0 | 403 | | rootTypeName._namespace = string.Empty; |
| | 0 | 404 | | } |
| | 0 | 405 | | } |
| | 0 | 406 | | _namespace = rootTypeName._namespace; |
| | 0 | 407 | | } |
| | | 408 | | |
| | 0 | 409 | | return _namespace; |
| | 0 | 410 | | } |
| | | 411 | | } |
| | | 412 | | |
| | | 413 | | /// <summary> |
| | | 414 | | /// Represents the total number of <see cref="TypeName"/> instances that are used to describe |
| | | 415 | | /// this instance, including any generic arguments or underlying types. |
| | | 416 | | /// </summary> |
| | | 417 | | /// <remarks> |
| | | 418 | | /// <para>This value is computed every time this method gets called, it's not cached.</para> |
| | | 419 | | /// <para>There's not really a parallel concept to this in reflection. Think of it |
| | | 420 | | /// as the total number of <see cref="TypeName"/> instances that would be created if |
| | | 421 | | /// you were to totally deconstruct this instance and visit each intermediate <see cref="TypeName"/> |
| | | 422 | | /// that occurs as part of deconstruction.</para> |
| | | 423 | | /// <para>"int" and "Person" each have complexities of 1 because they're standalone types.</para> |
| | | 424 | | /// <para>"int[]" has a node count of 2 because to fully inspect it involves inspecting the |
| | | 425 | | /// array type itself, <em>plus</em> unwrapping the underlying type ("int") and inspecting that.</para> |
| | | 426 | | /// <para> |
| | | 427 | | /// "Dictionary<string, List<int[][]>>" has node count 8 because fully visiting it |
| | | 428 | | /// involves inspecting 8 <see cref="TypeName"/> instances total: |
| | | 429 | | /// <list type="bullet"> |
| | | 430 | | /// <item>Dictionary<string, List<int[][]>> (the original type)</item> |
| | | 431 | | /// <item>Dictionary`2 (the generic type definition)</item> |
| | | 432 | | /// <item>string (a type argument of Dictionary)</item> |
| | | 433 | | /// <item>List<int[][]> (a type argument of Dictionary)</item> |
| | | 434 | | /// <item>List`1 (the generic type definition)</item> |
| | | 435 | | /// <item>int[][] (a type argument of List)</item> |
| | | 436 | | /// <item>int[] (the underlying type of int[][])</item> |
| | | 437 | | /// <item>int (the underlying type of int[])</item> |
| | | 438 | | /// </list> |
| | | 439 | | /// </para> |
| | | 440 | | /// </remarks> |
| | | 441 | | /// <exception cref="OverflowException">The total number of <see cref="TypeName"/> instances that are used to de |
| | | 442 | | /// this instance exceed <see cref="int.MaxValue"/>.</exception> |
| | | 443 | | public int GetNodeCount() |
| | 68152 | 444 | | { |
| | | 445 | | // This method uses checked arithmetic to avoid silent overflows. |
| | | 446 | | // It's impossible to parse a TypeName with NodeCount > int.MaxValue |
| | | 447 | | // (TypeNameParseOptions.MaxNodes is an int), but it's possible |
| | | 448 | | // to create such names with the Make* APIs. |
| | 68152 | 449 | | int result = 1; |
| | | 450 | | |
| | 68152 | 451 | | if (IsArray || IsPointer || IsByRef) |
| | 9408 | 452 | | { |
| | 9408 | 453 | | result = checked(result + GetElementType().GetNodeCount()); |
| | 9408 | 454 | | } |
| | 58744 | 455 | | else if (IsConstructedGenericType) |
| | 15312 | 456 | | { |
| | 15312 | 457 | | result = checked(result + GetGenericTypeDefinition().GetNodeCount()); |
| | | 458 | | |
| | 98272 | 459 | | foreach (TypeName genericArgument in GetGenericArguments()) |
| | 26168 | 460 | | { |
| | 26168 | 461 | | result = checked(result + genericArgument.GetNodeCount()); |
| | 26168 | 462 | | } |
| | 15312 | 463 | | } |
| | 43432 | 464 | | else if (IsNested) |
| | 2576 | 465 | | { |
| | 2576 | 466 | | result = checked(result + DeclaringType.GetNodeCount()); |
| | 2576 | 467 | | } |
| | | 468 | | |
| | 68152 | 469 | | return result; |
| | 68152 | 470 | | } |
| | | 471 | | |
| | | 472 | | /// <summary> |
| | | 473 | | /// The TypeName of the object encompassed or referred to by the current array, pointer, or reference type. |
| | | 474 | | /// </summary> |
| | | 475 | | /// <remarks> |
| | | 476 | | /// For example, given "int[][]", unwraps the outermost array and returns "int[]". |
| | | 477 | | /// </remarks> |
| | | 478 | | /// <exception cref="InvalidOperationException">The current type is not an array, pointer or reference.</excepti |
| | | 479 | | public TypeName GetElementType() |
| | 18432 | 480 | | { |
| | 18432 | 481 | | if (!(IsArray || IsPointer || IsByRef)) |
| | 0 | 482 | | { |
| | 0 | 483 | | TypeNameParserHelpers.ThrowInvalidOperation_NoElement(); |
| | | 484 | | } |
| | | 485 | | |
| | 18432 | 486 | | return _elementOrGenericType!; |
| | 18432 | 487 | | } |
| | | 488 | | |
| | | 489 | | /// <summary> |
| | | 490 | | /// Returns a TypeName object that represents a generic type name definition from which the current generic type |
| | | 491 | | /// </summary> |
| | | 492 | | /// <remarks> |
| | | 493 | | /// Given "Dictionary<string, int>", returns the generic type definition "Dictionary<,>". |
| | | 494 | | /// </remarks> |
| | | 495 | | /// <exception cref="InvalidOperationException">The current type is not a generic type.</exception> |
| | | 496 | | public TypeName GetGenericTypeDefinition() |
| | 25648 | 497 | | { |
| | 25648 | 498 | | if (!IsConstructedGenericType) |
| | 0 | 499 | | { |
| | 0 | 500 | | TypeNameParserHelpers.ThrowInvalidOperation_NotGenericType(); |
| | | 501 | | } |
| | | 502 | | |
| | 25648 | 503 | | return _elementOrGenericType!; |
| | 25648 | 504 | | } |
| | | 505 | | |
| | | 506 | | /// <summary> |
| | | 507 | | /// Parses a span of characters into a type name. |
| | | 508 | | /// </summary> |
| | | 509 | | /// <param name="typeName">A span containing the characters representing the type name to parse.</param> |
| | | 510 | | /// <param name="options">An object that describes optional <seealso cref="TypeNameParseOptions"/> parameters to |
| | | 511 | | /// <returns>Parsed type name.</returns> |
| | | 512 | | /// <exception cref="ArgumentException">Provided type name was invalid.</exception> |
| | | 513 | | /// <exception cref="InvalidOperationException">Parsing has exceeded the limit set by <seealso cref="TypeNamePar |
| | | 514 | | public static TypeName Parse(ReadOnlySpan<char> typeName, TypeNameParseOptions? options = default) |
| | 7722 | 515 | | => TypeNameParser.Parse(typeName, throwOnError: true, options)!; |
| | | 516 | | |
| | | 517 | | /// <summary> |
| | | 518 | | /// Tries to parse a span of characters into a type name. |
| | | 519 | | /// </summary> |
| | | 520 | | /// <param name="typeName">A span containing the characters representing the type name to parse.</param> |
| | | 521 | | /// <param name="options">An object that describes optional <seealso cref="TypeNameParseOptions"/> parameters to |
| | | 522 | | /// <param name="result">Contains the result when parsing succeeds.</param> |
| | | 523 | | /// <returns>true if type name was converted successfully, otherwise, false.</returns> |
| | | 524 | | public static bool TryParse(ReadOnlySpan<char> typeName, [NotNullWhen(true)] out TypeName? result, TypeNameParse |
| | 7722 | 525 | | { |
| | 7722 | 526 | | result = TypeNameParser.Parse(typeName, throwOnError: false, options); |
| | 7722 | 527 | | return result is not null; |
| | 7722 | 528 | | } |
| | | 529 | | |
| | | 530 | | /// <summary> |
| | | 531 | | /// Converts any escaped characters in the input type name or namespace. |
| | | 532 | | /// </summary> |
| | | 533 | | /// <param name="name">The input string containing the name to convert.</param> |
| | | 534 | | /// <returns>A string of characters with any escaped characters converted to their unescaped form.</returns> |
| | | 535 | | /// <remarks> |
| | | 536 | | /// <para>The unescaped string can be used for looking up the type name or namespace in metadata.</para> |
| | | 537 | | /// <para>This method removes escape characters even if they precede a character that does not require escaping. |
| | | 538 | | /// </remarks> |
| | | 539 | | public static string Unescape(string name) |
| | 0 | 540 | | { |
| | 0 | 541 | | if (name is null) |
| | 0 | 542 | | { |
| | 0 | 543 | | TypeNameParserHelpers.ThrowArgumentNullException(nameof(name)); |
| | | 544 | | } |
| | | 545 | | |
| | 0 | 546 | | return TypeNameParserHelpers.Unescape(name); |
| | 0 | 547 | | } |
| | | 548 | | |
| | | 549 | | /// <summary> |
| | | 550 | | /// Gets the number of dimensions in an array. |
| | | 551 | | /// </summary> |
| | | 552 | | /// <returns>An integer that contains the number of dimensions in the current type.</returns> |
| | | 553 | | /// <exception cref="InvalidOperationException">The current type is not an array.</exception> |
| | | 554 | | public int GetArrayRank() |
| | 0 | 555 | | { |
| | 0 | 556 | | if (!(_rankOrModifier == TypeNameParserHelpers.SZArray || _rankOrModifier > 0)) |
| | 0 | 557 | | { |
| | 0 | 558 | | TypeNameParserHelpers.ThrowInvalidOperation_HasToBeArrayClass(); |
| | | 559 | | } |
| | | 560 | | |
| | 0 | 561 | | return _rankOrModifier == TypeNameParserHelpers.SZArray ? 1 : _rankOrModifier; |
| | 0 | 562 | | } |
| | | 563 | | |
| | | 564 | | /// <summary> |
| | | 565 | | /// If this <see cref="TypeName"/> represents a constructed generic type, returns an array |
| | | 566 | | /// of all the generic arguments. Otherwise it returns an empty array. |
| | | 567 | | /// </summary> |
| | | 568 | | /// <remarks> |
| | | 569 | | /// <para>For example, given "Dictionary<string, int>", returns a 2-element array containing |
| | | 570 | | /// string and int.</para> |
| | | 571 | | /// </remarks> |
| | | 572 | | public |
| | | 573 | | #if SYSTEM_PRIVATE_CORELIB |
| | | 574 | | ReadOnlySpan<TypeName> GetGenericArguments() => CollectionsMarshal.AsSpan(_genericArguments); |
| | | 575 | | #else |
| | 22968 | 576 | | ImmutableArray<TypeName> GetGenericArguments() => _genericArguments; |
| | | 577 | | #endif |
| | | 578 | | |
| | | 579 | | #if SYSTEM_REFLECTION_METADATA |
| | | 580 | | /// <summary> |
| | | 581 | | /// Creates a new <see cref="TypeName" /> object that represents current simple name with provided assembly name |
| | | 582 | | /// </summary> |
| | | 583 | | /// <param name="assemblyName">Assembly name.</param> |
| | | 584 | | /// <returns>Created simple name.</returns> |
| | | 585 | | /// <exception cref="InvalidOperationException">The current type name is not simple.</exception> |
| | | 586 | | public TypeName WithAssemblyName(AssemblyNameInfo? assemblyName) |
| | 0 | 587 | | { |
| | | 588 | | // Recursive method. See comments in FullName property getter for more information |
| | | 589 | | // on how this is protected against attack. |
| | | 590 | | // |
| | | 591 | | // n.b. AssemblyNameInfo could also be hostile. The typical exploit is that a single |
| | | 592 | | // long AssemblyNameInfo is associated with one or more simple TypeName objects, |
| | | 593 | | // leading to an alg. complexity attack (DoS). It's important that TypeName doesn't |
| | | 594 | | // actually *do* anything with the provided AssemblyNameInfo rather than store it. |
| | | 595 | | // For example, don't use it inside a string concat operation unless the caller |
| | | 596 | | // explicitly requested that to happen. If the input is hostile, the caller should |
| | | 597 | | // never perform such concats in a loop. |
| | | 598 | | |
| | 0 | 599 | | if (!IsSimple) |
| | 0 | 600 | | { |
| | 0 | 601 | | TypeNameParserHelpers.ThrowInvalidOperation_NotSimpleName(FullName); |
| | | 602 | | } |
| | | 603 | | |
| | 0 | 604 | | TypeName? declaringType = IsNested |
| | 0 | 605 | | ? DeclaringType.WithAssemblyName(assemblyName) |
| | 0 | 606 | | : null; |
| | | 607 | | |
| | 0 | 608 | | return new TypeName(fullName: _fullName, |
| | 0 | 609 | | assemblyName: assemblyName, |
| | 0 | 610 | | elementOrGenericType: null, |
| | 0 | 611 | | declaringType: declaringType, |
| | 0 | 612 | | genericTypeArguments: ImmutableArray<TypeName>.Empty, |
| | 0 | 613 | | nestedNameLength: _nestedNameLength); |
| | 0 | 614 | | } |
| | | 615 | | |
| | | 616 | | /// <summary> |
| | | 617 | | /// Creates a <see cref="TypeName" /> object representing a one-dimensional array |
| | | 618 | | /// of the current type, with a lower bound of zero. |
| | | 619 | | /// </summary> |
| | | 620 | | /// <returns> |
| | | 621 | | /// A <see cref="TypeName" /> object representing a one-dimensional array |
| | | 622 | | /// of the current type, with a lower bound of zero. |
| | | 623 | | /// </returns> |
| | 0 | 624 | | public TypeName MakeSZArrayTypeName() => MakeElementTypeName(TypeNameParserHelpers.SZArray); |
| | | 625 | | |
| | | 626 | | /// <summary> |
| | | 627 | | /// Creates a <see cref="TypeName" /> object representing an array of the current type, |
| | | 628 | | /// with the specified number of dimensions. |
| | | 629 | | /// </summary> |
| | | 630 | | /// <param name="rank">The number of dimensions for the array. This number must be more than zero and less than |
| | | 631 | | /// <returns> |
| | | 632 | | /// A <see cref="TypeName" /> object representing an array of the current type, |
| | | 633 | | /// with the specified number of dimensions. |
| | | 634 | | /// </returns> |
| | | 635 | | /// <exception cref="ArgumentOutOfRangeException">rank is invalid. For example, 0 or negative.</exception> |
| | | 636 | | public TypeName MakeArrayTypeName(int rank) |
| | 0 | 637 | | => rank <= 0 |
| | 0 | 638 | | ? throw new ArgumentOutOfRangeException(nameof(rank)) |
| | 0 | 639 | | : MakeElementTypeName(rank); |
| | | 640 | | |
| | | 641 | | /// <summary> |
| | | 642 | | /// Creates a <see cref="TypeName" /> object that represents a pointer to the current type. |
| | | 643 | | /// </summary> |
| | | 644 | | /// <returns> |
| | | 645 | | /// A <see cref="TypeName" /> object that represents a pointer to the current type. |
| | | 646 | | /// </returns> |
| | 0 | 647 | | public TypeName MakePointerTypeName() => MakeElementTypeName(TypeNameParserHelpers.Pointer); |
| | | 648 | | |
| | | 649 | | /// <summary> |
| | | 650 | | /// Creates a <see cref="TypeName" /> object that represents a managed reference to the current type. |
| | | 651 | | /// </summary> |
| | | 652 | | /// <returns> |
| | | 653 | | /// A <see cref="TypeName" /> object that represents a managed reference to the current type. |
| | | 654 | | /// </returns> |
| | 0 | 655 | | public TypeName MakeByRefTypeName() => MakeElementTypeName(TypeNameParserHelpers.ByRef); |
| | | 656 | | |
| | | 657 | | /// <summary> |
| | | 658 | | /// Creates a new constructed generic type name. |
| | | 659 | | /// </summary> |
| | | 660 | | /// <param name="typeArguments">An array of type names to be used as generic arguments of the current simple typ |
| | | 661 | | /// <returns> |
| | | 662 | | /// A <see cref="TypeName" /> representing the constructed type name formed by using the elements |
| | | 663 | | /// of <paramref name="typeArguments"/> for the generic arguments of the current simple type name. |
| | | 664 | | /// </returns> |
| | | 665 | | /// <exception cref="InvalidOperationException">The current type name is not simple.</exception> |
| | | 666 | | public TypeName MakeGenericTypeName(ImmutableArray<TypeName> typeArguments) |
| | 0 | 667 | | { |
| | 0 | 668 | | if (!IsSimple) |
| | 0 | 669 | | { |
| | 0 | 670 | | TypeNameParserHelpers.ThrowInvalidOperation_NotSimpleName(FullName); |
| | | 671 | | } |
| | | 672 | | |
| | 0 | 673 | | return new TypeName(fullName: null, AssemblyName, elementOrGenericType: this, declaringType: _declaringType, |
| | 0 | 674 | | } |
| | | 675 | | |
| | | 676 | | private TypeName MakeElementTypeName(int rankOrModifier) |
| | 0 | 677 | | => new TypeName( |
| | 0 | 678 | | fullName: null, |
| | 0 | 679 | | assemblyName: AssemblyName, |
| | 0 | 680 | | elementOrGenericType: this, |
| | 0 | 681 | | declaringType: null, |
| | 0 | 682 | | genericTypeArguments: ImmutableArray<TypeName>.Empty, |
| | 0 | 683 | | rankOrModifier: rankOrModifier); |
| | | 684 | | #endif |
| | | 685 | | } |
| | | 686 | | } |
| | | 687 | | |