// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software // without restriction, including without limitation the rights to use, copy, modify, merge, // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons // to whom the Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; using System.Linq; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Semantics; using ICSharpCode.Decompiler.TypeSystem.Implementation; using ICSharpCode.Decompiler.Util; namespace ICSharpCode.Decompiler.TypeSystem { /// /// Contains extension methods for the type system. /// public static class TypeSystemExtensions { #region GetAllBaseTypes /// /// Gets all base types. /// /// This is the reflexive and transitive closure of . /// Note that this method does not return all supertypes - doing so is impossible due to contravariance /// (and undesirable for covariance as the list could become very large). /// /// The output is ordered so that base types occur before derived types. /// public static IEnumerable GetAllBaseTypes(this IType type) { if (type == null) throw new ArgumentNullException(nameof(type)); BaseTypeCollector collector = new BaseTypeCollector(); collector.CollectBaseTypes(type); return collector; } /// /// Gets all non-interface base types. /// /// /// When is an interface, this method will also return base interfaces (return same output as GetAllBaseTypes()). /// /// The output is ordered so that base types occur before derived types. /// public static IEnumerable GetNonInterfaceBaseTypes(this IType type) { if (type == null) throw new ArgumentNullException(nameof(type)); BaseTypeCollector collector = new BaseTypeCollector(); collector.SkipImplementedInterfaces = true; collector.CollectBaseTypes(type); return collector; } #endregion #region GetAllBaseTypeDefinitions /// /// Gets all base type definitions. /// The output is ordered so that base types occur before derived types. /// /// /// This is equivalent to type.GetAllBaseTypes().Select(t => t.GetDefinition()).Where(d => d != null).Distinct(). /// public static IEnumerable GetAllBaseTypeDefinitions(this IType type) { if (type == null) throw new ArgumentNullException(nameof(type)); return type.GetAllBaseTypes().Select(t => t.GetDefinition()).Where(d => d != null).Distinct(); } /// /// Gets whether this type definition is derived from the base type definition. /// public static bool IsDerivedFrom(this ITypeDefinition type, ITypeDefinition baseType) { if (type == null) throw new ArgumentNullException(nameof(type)); if (baseType == null) return false; if (type.Compilation != baseType.Compilation) { throw new InvalidOperationException("Both arguments to IsDerivedFrom() must be from the same compilation."); } return type.GetAllBaseTypeDefinitions().Contains(baseType); } /// /// Gets whether this type definition is derived from a given known type. /// public static bool IsDerivedFrom(this ITypeDefinition type, KnownTypeCode baseType) { if (type == null) throw new ArgumentNullException(nameof(type)); if (baseType == KnownTypeCode.None) return false; return IsDerivedFrom(type, type.Compilation.FindType(baseType).GetDefinition()); } #endregion #region GetDeclaringTypeDefinitionsOrThis /// /// Returns all declaring type definitions of this type definition. /// The output is ordered so that inner types occur before outer types. /// public static IEnumerable GetDeclaringTypeDefinitions(this ITypeDefinition definition) { if (definition == null) { throw new ArgumentNullException(nameof(definition)); } while (definition != null) { yield return definition; definition = definition.DeclaringTypeDefinition; } } #endregion #region IsOpen / IsUnbound / IsUnmanagedType / IsKnownType sealed class TypeClassificationVisitor : TypeVisitor { internal bool isOpen; internal IEntity typeParameterOwner; int typeParameterOwnerNestingLevel; public override IType VisitTypeParameter(ITypeParameter type) { isOpen = true; // If both classes and methods, or different classes (nested types) // are involved, find the most specific one int newNestingLevel = GetNestingLevel(type.Owner); if (newNestingLevel > typeParameterOwnerNestingLevel) { typeParameterOwner = type.Owner; typeParameterOwnerNestingLevel = newNestingLevel; } return base.VisitTypeParameter(type); } static int GetNestingLevel(IEntity entity) { int level = 0; while (entity != null) { level++; entity = entity.DeclaringTypeDefinition; } return level; } } /// /// Gets whether the type is an open type (contains type parameters). /// /// /// /// class X<T> { /// List<T> open; /// X<X<T[]>> open; /// X<string> closed; /// int closed; /// } /// /// public static bool IsOpen(this IType type) { if (type == null) throw new ArgumentNullException(nameof(type)); TypeClassificationVisitor v = new TypeClassificationVisitor(); type.AcceptVisitor(v); return v.isOpen; } /// /// Gets the entity that owns the type parameters occurring in the specified type. /// If both class and method type parameters are present, the method is returned. /// Returns null if the specified type is closed. /// /// static IEntity GetTypeParameterOwner(IType type) { if (type == null) throw new ArgumentNullException(nameof(type)); TypeClassificationVisitor v = new TypeClassificationVisitor(); type.AcceptVisitor(v); return v.typeParameterOwner; } /// /// Gets whether the type is unbound (is a generic type, but no type arguments were provided). /// /// /// In "typeof(List<Dictionary<,>>)", only the Dictionary is unbound, the List is considered /// bound despite containing an unbound type. /// This method returns false for partially parameterized types (Dictionary<string, >). /// public static bool IsUnbound(this IType type) { if (type == null) throw new ArgumentNullException(nameof(type)); return (type is ITypeDefinition || type is UnknownType) && type.TypeParameterCount > 0; } /// /// Gets whether the type is considered unmanaged. /// /// /// The C# 6.0 spec lists the following criteria: An unmanaged type is one of the following /// * sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, or bool /// * any enum type /// * any pointer type /// * any user-defined struct type that is not a constructed (= generic) type and contains fields of unmanaged types only. /// /// C# 8.0 removes the restriction that constructed (= generic) types are not considered unmanaged types. /// public static bool IsUnmanagedType(this IType type, bool allowGenerics) { HashSet types = null; return IsUnmanagedTypeInternal(type); bool IsUnmanagedTypeInternal(IType type) { if (type.Kind is TypeKind.Enum or TypeKind.Pointer or TypeKind.FunctionPointer) { return true; } if (type is ITypeParameter tp) { return tp.HasUnmanagedConstraint; } var def = type.GetDefinition(); if (def == null) { return false; } switch (def.KnownTypeCode) { case KnownTypeCode.Void: case KnownTypeCode.Boolean: case KnownTypeCode.Char: case KnownTypeCode.SByte: case KnownTypeCode.Byte: case KnownTypeCode.Int16: case KnownTypeCode.UInt16: case KnownTypeCode.Int32: case KnownTypeCode.UInt32: case KnownTypeCode.Int64: case KnownTypeCode.UInt64: case KnownTypeCode.Decimal: case KnownTypeCode.Single: case KnownTypeCode.Double: case KnownTypeCode.IntPtr: case KnownTypeCode.UIntPtr: case KnownTypeCode.TypedReference: //case KnownTypeCode.ArgIterator: //case KnownTypeCode.RuntimeArgumentHandle: return true; } if (type.Kind == TypeKind.Struct) { if (!allowGenerics && def.TypeParameterCount > 0) { return false; } if (types == null) { types = new HashSet(); } types.Add(type); foreach (var f in type.GetFields()) { if (types.Contains(f.Type)) { types.Remove(type); return false; } if (!IsUnmanagedTypeInternal(f.Type)) { types.Remove(type); return false; } } types.Remove(type); return true; } return false; } } /// /// Gets whether the type is the specified known type. /// For generic known types, this returns true for any parameterization of the type (and also for the definition itself). /// public static bool IsKnownType(this IType type, KnownTypeCode knownType) { var def = type.GetDefinition(); return def != null && def.KnownTypeCode == knownType; } /// /// Gets whether the type is the specified known type. /// For generic known types, this returns true for any parameterization of the type (and also for the definition itself). /// internal static bool IsKnownType(this IType type, KnownAttribute knownType) { var def = type.GetDefinition(); return def != null && def.FullTypeName.IsKnownType(knownType); } public static bool IsKnownType(this FullTypeName typeName, KnownTypeCode knownType) { return typeName == KnownTypeReference.Get(knownType).TypeName; } public static bool IsKnownType(this TopLevelTypeName typeName, KnownTypeCode knownType) { return typeName == KnownTypeReference.Get(knownType).TypeName; } internal static bool IsKnownType(this FullTypeName typeName, KnownAttribute knownType) { return typeName == knownType.GetTypeName(); } internal static bool IsKnownType(this TopLevelTypeName typeName, KnownAttribute knownType) { return typeName == knownType.GetTypeName(); } #endregion #region GetDelegateInvokeMethod /// /// Gets the invoke method for a delegate type. /// /// /// Returns null if the type is not a delegate type; or if the invoke method could not be found. /// public static IMethod GetDelegateInvokeMethod(this IType type) { if (type == null) throw new ArgumentNullException(nameof(type)); if (type.Kind == TypeKind.Delegate) return type.GetMethods(m => m.Name == "Invoke", GetMemberOptions.IgnoreInheritedMembers).FirstOrDefault(); else return null; } #endregion public static IType SkipModifiers(this IType ty) { while (ty is ModifiedType mt) { ty = mt.ElementType; } return ty; } public static bool HasReadonlyModifier(this IMethod accessor) { return accessor.ThisIsRefReadOnly && accessor.DeclaringTypeDefinition?.IsReadOnly == false; } public static bool IsAnyPointer(this TypeKind typeKind) { return typeKind switch { TypeKind.Pointer => true, TypeKind.FunctionPointer => true, _ => false }; } #region GetType/Member /// /// Gets all type definitions in the compilation. /// This may include types from referenced assemblies that are not accessible in the main assembly. /// public static IEnumerable GetAllTypeDefinitions(this ICompilation compilation) { return compilation.Modules.SelectMany(a => a.TypeDefinitions); } /// /// Gets all top level type definitions in the compilation. /// This may include types from referenced assemblies that are not accessible in the main assembly. /// public static IEnumerable GetTopLevelTypeDefinitions(this ICompilation compilation) { return compilation.Modules.SelectMany(a => a.TopLevelTypeDefinitions); } #endregion #region Resolve on collections public static IReadOnlyList Resolve(this IList typeReferences, ITypeResolveContext context) { if (typeReferences == null) throw new ArgumentNullException(nameof(typeReferences)); if (typeReferences.Count == 0) return EmptyList.Instance; else return new ProjectedList(context, typeReferences, (c, t) => t.Resolve(c)); } // There is intentionally no Resolve() overload for IList: the resulting IList would // contains nulls when there are resolve errors. #endregion #region IAssembly.GetTypeDefinition() /// /// Retrieves the specified type in this compilation. /// Returns an if the type cannot be found in this compilation. /// /// /// There can be multiple types with the same full name in a compilation, as a /// full type name is only unique per assembly. /// If there are multiple possible matches, this method will return just one of them. /// When possible, use instead to /// retrieve a type from a specific assembly. /// public static IType FindType(this ICompilation compilation, FullTypeName fullTypeName) { if (compilation == null) throw new ArgumentNullException(nameof(compilation)); foreach (IModule asm in compilation.Modules) { ITypeDefinition def = asm.GetTypeDefinition(fullTypeName); if (def != null) return def; } return new UnknownType(fullTypeName); } /// /// Gets the type definition for the specified unresolved type. /// Returns null if the unresolved type does not belong to this assembly. /// public static ITypeDefinition GetTypeDefinition(this IModule module, FullTypeName fullTypeName) { if (module == null) throw new ArgumentNullException("assembly"); TopLevelTypeName topLevelTypeName = fullTypeName.TopLevelTypeName; ITypeDefinition typeDef = module.GetTypeDefinition(topLevelTypeName); if (typeDef == null) return null; int typeParameterCount = topLevelTypeName.TypeParameterCount; for (int i = 0; i < fullTypeName.NestingLevel; i++) { string name = fullTypeName.GetNestedTypeName(i); typeParameterCount += fullTypeName.GetNestedTypeAdditionalTypeParameterCount(i); typeDef = FindNestedType(typeDef, name, typeParameterCount); if (typeDef == null) break; } return typeDef; } static ITypeDefinition FindNestedType(ITypeDefinition typeDef, string name, int typeParameterCount) { foreach (var nestedType in typeDef.NestedTypes) { if (nestedType.Name == name && nestedType.TypeParameterCount == typeParameterCount) return nestedType; } return null; } #endregion #region IEntity.GetAttribute /// /// Gets whether the entity has an attribute of the specified attribute type. /// /// The entity on which the attributes are declared. /// The attribute type to look for. /// /// Specifies whether attributes inherited from base classes and base members /// (if the given in an override) /// should be returned. /// public static bool HasAttribute(this IEntity entity, KnownAttribute attributeType, bool inherit) { if (!inherit) return entity.HasAttribute(attributeType); return GetAttribute(entity, attributeType, inherit) != null; } /// /// Gets the attribute of the specified attribute type. /// /// The entity on which the attributes are declared. /// The attribute type to look for. /// /// Specifies whether attributes inherited from base classes and base members /// (if the given in an override) /// should be returned. /// /// /// Returns the attribute that was found; or null if none was found. /// If inherit is true, an from the entity itself will be returned if possible; /// and the base entity will only be searched if none exists. /// public static IAttribute GetAttribute(this IEntity entity, KnownAttribute attributeType, bool inherit) { if (inherit) { if (entity is ITypeDefinition td) { return InheritanceHelper.GetAttribute(td, attributeType); } else if (entity is IMember m) { return InheritanceHelper.GetAttribute(m, attributeType); } else { throw new NotSupportedException("Unknown entity type"); } } else { return entity.GetAttribute(attributeType); } } /// /// Gets the attributes on the entity. /// /// The entity on which the attributes are declared. /// /// Specifies whether attributes inherited from base classes and base members /// (if the given in an override) /// should be returned. /// /// /// Returns the list of attributes that were found. /// If inherit is true, attributes from the entity itself are returned first; /// followed by attributes inherited from the base entity. /// public static IEnumerable GetAttributes(this IEntity entity, bool inherit) { if (inherit) { if (entity is ITypeDefinition td) { return InheritanceHelper.GetAttributes(td); } else if (entity is IMember m) { return InheritanceHelper.GetAttributes(m); } else { throw new NotSupportedException("Unknown entity type"); } } else { return entity.GetAttributes(); } } #endregion #region IParameter.GetAttribute /// /// Gets whether the parameter has an attribute of the specified attribute type. /// /// The parameter on which the attributes are declared. /// The attribute type to look for. public static bool HasAttribute(this IParameter parameter, KnownAttribute attributeType) { return GetAttribute(parameter, attributeType) != null; } /// /// Gets the attribute of the specified attribute type. /// /// The parameter on which the attributes are declared. /// The attribute type to look for. /// /// Returns the attribute that was found; or null if none was found. /// public static IAttribute GetAttribute(this IParameter parameter, KnownAttribute attributeType) { return parameter.GetAttributes().FirstOrDefault(a => a.AttributeType.IsKnownType(attributeType)); } #endregion #region IAssembly.GetTypeDefinition(string,string,int) /// /// Gets the type definition for a top-level type. /// /// This method uses ordinal name comparison, not the compilation's name comparer. public static ITypeDefinition GetTypeDefinition(this IModule module, string namespaceName, string name, int typeParameterCount = 0) { if (module == null) throw new ArgumentNullException("assembly"); return module.GetTypeDefinition(new TopLevelTypeName(namespaceName, name, typeParameterCount)); } #endregion #region ResolveResult public static ISymbol GetSymbol(this ResolveResult rr) { if (rr is LocalResolveResult) { return ((LocalResolveResult)rr).Variable; } else if (rr is MemberResolveResult) { return ((MemberResolveResult)rr).Member; } else if (rr is TypeResolveResult) { return ((TypeResolveResult)rr).Type.GetDefinition(); } else if (rr is ConversionResolveResult) { return ((ConversionResolveResult)rr).Input.GetSymbol(); } return null; } #endregion public static IType GetElementTypeFromIEnumerable(this IType collectionType, ICompilation compilation, bool allowIEnumerator, out bool? isGeneric) { bool foundNonGenericIEnumerable = false; foreach (IType baseType in collectionType.GetAllBaseTypes()) { ITypeDefinition baseTypeDef = baseType.GetDefinition(); if (baseTypeDef != null) { KnownTypeCode typeCode = baseTypeDef.KnownTypeCode; if (typeCode == KnownTypeCode.IEnumerableOfT || (allowIEnumerator && typeCode == KnownTypeCode.IEnumeratorOfT)) { ParameterizedType pt = baseType as ParameterizedType; if (pt != null) { isGeneric = true; return pt.GetTypeArgument(0); } } if (typeCode == KnownTypeCode.IEnumerable || (allowIEnumerator && typeCode == KnownTypeCode.IEnumerator)) foundNonGenericIEnumerable = true; } } // System.Collections.IEnumerable found in type hierarchy -> Object is element type. if (foundNonGenericIEnumerable) { isGeneric = false; return compilation.FindType(KnownTypeCode.Object); } isGeneric = null; return SpecialType.UnknownType; } public static bool FullNameIs(this IMember member, string type, string name) { return member.Name == name && member.DeclaringType?.FullName == type; } public static KnownAttribute IsBuiltinAttribute(this ITypeDefinition type) { return KnownAttributes.IsKnownAttributeType(type); } public static IType WithoutNullability(this IType type) { return type.ChangeNullability(Nullability.Oblivious); } public static bool IsDirectImportOf(this ITypeDefinition type, IModule module) { var moduleReference = type.ParentModule; foreach (var asmRef in module.PEFile.AssemblyReferences) { if (asmRef.FullName == moduleReference.FullAssemblyName) return true; if (asmRef.Name == "netstandard" && asmRef.GetPublicKeyToken() != null) { var referencedModule = module.Compilation.FindModuleByReference(asmRef); if (referencedModule != null && !referencedModule.PEFile.GetTypeForwarder(type.FullTypeName).IsNil) return true; } } return false; } public static IModule FindModuleByReference(this ICompilation compilation, IAssemblyReference assemblyName) { foreach (var module in compilation.Modules) { if (string.Equals(module.FullAssemblyName, assemblyName.FullName, StringComparison.OrdinalIgnoreCase)) { return module; } } foreach (var module in compilation.Modules) { if (string.Equals(module.Name, assemblyName.Name, StringComparison.OrdinalIgnoreCase)) { return module; } } return null; } /// /// When given a generic type definition, returns the self-parameterized type /// (i.e. the type of "this" within the type definition). /// When given a non-generic type definition, returns that definition unchanged. /// public static IType AsParameterizedType(this ITypeDefinition td) { if (td.TypeParameterCount == 0) { return td; } else { return new ParameterizedType(td, td.TypeArguments); } } } }