static void PopulateGenericParameters(IGenericParameterProvider publicType, CodeTypeParameterCollection parameters, AttributeFilter attributeFilter, Func <GenericParameter, bool> shouldUseParameter) { foreach (var parameter in publicType.GenericParameters.Where(shouldUseParameter)) { // A little hacky. Means we get "in" and "out" prefixed on any constraints, but it's either that // or add it as a custom attribute var name = parameter.Name; if (parameter.IsCovariant) { name = "out " + name; } if (parameter.IsContravariant) { name = "in " + name; } var attributeCollection = new CodeAttributeDeclarationCollection(); if (parameter.HasCustomAttributes) { PopulateCustomAttributes(parameter, attributeCollection, attributeFilter); } var typeParameter = new CodeTypeParameter(name) { HasConstructorConstraint = parameter.HasDefaultConstructorConstraint && !parameter.HasNotNullableValueTypeConstraint }; typeParameter.CustomAttributes.AddRange(attributeCollection.OfType <CodeAttributeDeclaration>().ToArray()); var nullableConstraint = parameter.GetNullabilityMap().First(); var unmanagedConstraint = parameter.CustomAttributes.Any(attr => attr.AttributeType.FullName == "System.Runtime.CompilerServices.IsUnmanagedAttribute"); if (parameter.HasNotNullableValueTypeConstraint) { typeParameter.Constraints.Add(unmanagedConstraint ? " unmanaged" : " struct"); } if (parameter.HasReferenceTypeConstraint) { typeParameter.Constraints.Add(nullableConstraint == true ? " class?" : " class"); } else if (nullableConstraint == false) { typeParameter.Constraints.Add(" notnull"); } using (NullableContext.Push(parameter)) { foreach (var constraint in parameter.Constraints.Where(constraint => !IsSpecialConstraint(constraint))) { // for generic constraints like IEnumerable<T> call to GetElementType() returns TypeReference with Name = !0 var typeReference = constraint.ConstraintType /*.GetElementType()*/.CreateCodeTypeReference(constraint); typeParameter.Constraints.Add(typeReference); } } parameters.Add(typeParameter); } bool IsSpecialConstraint(GenericParameterConstraint constraint) { // struct if (constraint.ConstraintType is TypeReference reference && reference.FullName == "System.ValueType") { return(true); } // unmanaged if (constraint.ConstraintType.IsUnmanaged()) { return(true); } return(false); } }
static CodeTypeDeclaration CreateTypeDeclaration(TypeDefinition publicType, string[] whitelistedNamespacePrefixes, AttributeFilter attributeFilter) { if (publicType.IsDelegate()) { return(CreateDelegateDeclaration(publicType, attributeFilter)); } var @static = false; TypeAttributes attributes = 0; if (publicType.IsPublic || publicType.IsNestedPublic) { attributes |= TypeAttributes.Public; } if (publicType.IsNestedFamily || publicType.IsNestedFamilyOrAssembly) { attributes |= TypeAttributes.NestedFamily; } if (publicType.IsSealed && !publicType.IsAbstract) { attributes |= TypeAttributes.Sealed; } else if (!publicType.IsSealed && publicType.IsAbstract && !publicType.IsInterface) { attributes |= TypeAttributes.Abstract; } else if (publicType.IsSealed && publicType.IsAbstract) { @static = true; } // Static support is a hack. CodeDOM does support it, and this isn't // correct C#, but it's good enough for our API outline var name = publicType.Name; var isStruct = publicType.IsValueType && !publicType.IsPrimitive && !publicType.IsEnum; var @readonly = isStruct && publicType.CustomAttributes.Any(a => a.AttributeType.FullName == "System.Runtime.CompilerServices.IsReadOnlyAttribute"); var index = name.IndexOf('`'); if (index != -1) { name = name.Substring(0, index); } var declarationName = string.Empty; if (@readonly) { declarationName += CodeNormalizer.ReadonlyMarker; } if (@static) { declarationName += CodeNormalizer.StaticMarker; } declarationName += name; var declaration = new CodeTypeDeclaration(declarationName) { CustomAttributes = CreateCustomAttributes(publicType, attributeFilter), // TypeAttributes must be specified before the IsXXX as they manipulate TypeAttributes! TypeAttributes = attributes, IsClass = publicType.IsClass, IsEnum = publicType.IsEnum, IsInterface = publicType.IsInterface, IsStruct = isStruct, }; if (declaration.IsInterface && publicType.BaseType != null) { throw new NotImplementedException("Base types for interfaces needs testing"); } PopulateGenericParameters(publicType, declaration.TypeParameters, attributeFilter, parameter => { var declaringType = publicType.DeclaringType; while (declaringType != null) { if (declaringType.GenericParameters.Any(p => p.Name == parameter.Name)) { return(false); // https://github.com/ApiApprover/ApiApprover/issues/108 } declaringType = declaringType.DeclaringType; } return(true); }); if (publicType.BaseType != null && ShouldOutputBaseType(publicType)) { if (publicType.BaseType.FullName == "System.Enum") { var underlyingType = publicType.GetEnumUnderlyingType(); if (underlyingType.FullName != "System.Int32") { declaration.BaseTypes.Add(underlyingType.CreateCodeTypeReference()); } } else { declaration.BaseTypes.Add(publicType.BaseType.CreateCodeTypeReference(publicType)); } } foreach (var @interface in publicType.Interfaces.OrderBy(i => i.InterfaceType.FullName, StringComparer.Ordinal) .Select(t => new { Reference = t, Definition = t.InterfaceType.Resolve() }) .Where(t => ShouldIncludeType(t.Definition)) .Select(t => t.Reference)) { declaration.BaseTypes.Add(@interface.InterfaceType.CreateCodeTypeReference(@interface)); } foreach (var memberInfo in publicType.GetMembers().Where(memberDefinition => ShouldIncludeMember(memberDefinition, whitelistedNamespacePrefixes)).OrderBy(m => m.Name, StringComparer.Ordinal)) { AddMemberToTypeDeclaration(declaration, memberInfo, attributeFilter); } // Fields should be in defined order for an enum var fields = !publicType.IsEnum ? publicType.Fields.OrderBy(f => f.Name, StringComparer.Ordinal) : (IEnumerable <FieldDefinition>)publicType.Fields; foreach (var field in fields) { AddMemberToTypeDeclaration(declaration, field, attributeFilter); } foreach (var nestedType in publicType.NestedTypes.Where(ShouldIncludeType).OrderBy(t => t.FullName, StringComparer.Ordinal)) { using (NullableContext.Push(nestedType)) { var nestedTypeDeclaration = CreateTypeDeclaration(nestedType, whitelistedNamespacePrefixes, attributeFilter); declaration.Members.Add(nestedTypeDeclaration); } } return(declaration); }