Skip to content

Commit

Permalink
Merge pull request #65 from JakeGinnivan/fix-type-attribute-handling
Browse files Browse the repository at this point in the history
Fix type attribute handling
  • Loading branch information
danielmarbach committed Jun 30, 2019
2 parents dbfb512 + 4da56b1 commit 56aa60b
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 9 deletions.
24 changes: 15 additions & 9 deletions src/PublicApiGenerator/ApiGenerator.cs
Expand Up @@ -226,7 +226,7 @@ static CodeTypeDeclaration CreateTypeDeclaration(TypeDefinition publicType, stri
if (declaration.IsInterface && publicType.BaseType != null)
throw new NotImplementedException("Base types for interfaces needs testing");

PopulateGenericParameters(publicType, declaration.TypeParameters);
PopulateGenericParameters(publicType, declaration.TypeParameters, excludeAttributes);

if (publicType.BaseType != null && ShouldOutputBaseType(publicType))
{
Expand Down Expand Up @@ -280,7 +280,7 @@ static CodeTypeDeclaration CreateDelegateDeclaration(TypeDefinition publicType,

// CodeDOM. No support. Return type attributes.
PopulateCustomAttributes(invokeMethod.MethodReturnType, declaration.CustomAttributes, type => ModifyCodeTypeReference(type, "return:"), excludeAttributes);
PopulateGenericParameters(publicType, declaration.TypeParameters);
PopulateGenericParameters(publicType, declaration.TypeParameters, excludeAttributes);
PopulateMethodParameters(invokeMethod, declaration.Parameters, excludeAttributes);

// Of course, CodeDOM doesn't support generic type parameters for delegates. Of course.
Expand All @@ -299,26 +299,32 @@ static bool ShouldOutputBaseType(TypeDefinition publicType)
return publicType.BaseType.FullName != "System.Object" && publicType.BaseType.FullName != "System.ValueType";
}

static void PopulateGenericParameters(IGenericParameterProvider publicType, CodeTypeParameterCollection parameters)
static void PopulateGenericParameters(IGenericParameterProvider publicType, CodeTypeParameterCollection parameters, HashSet<string> excludeAttributes)
{
foreach (var parameter in publicType.GenericParameters)
{
if (parameter.HasCustomAttributes)
throw new NotImplementedException("Attributes on type parameters is not supported. And weird");

// 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, which looks even weirder
// 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, excludeAttributes);
}

var typeParameter = new CodeTypeParameter(name)
{
HasConstructorConstraint =
parameter.HasDefaultConstructorConstraint && !parameter.HasNotNullableValueTypeConstraint
};

typeParameter.CustomAttributes.AddRange(attributeCollection.OfType<CodeAttributeDeclaration>().ToArray());

if (parameter.HasNotNullableValueTypeConstraint)
typeParameter.Constraints.Add(" struct"); // Extra space is a hack!
if (parameter.HasReferenceTypeConstraint)
Expand Down Expand Up @@ -428,7 +434,7 @@ static string ConvertAttributeToCode(Func<CodeTypeReference, CodeTypeReference>
static bool ShouldIncludeAttribute(CustomAttribute attribute, HashSet<string> excludeAttributes)
{
var attributeTypeDefinition = attribute.AttributeType.Resolve();
return attributeTypeDefinition != null && !excludeAttributes.Contains(attribute.AttributeType.FullName) && attributeTypeDefinition.IsPublic;
return attributeTypeDefinition != null && !excludeAttributes.Contains(attribute.AttributeType.FullName) && (attributeTypeDefinition.IsPublic || attributeTypeDefinition.FullName == "System.Runtime.CompilerServices.NullableAttribute");
}

static CodeExpression CreateInitialiserExpression(CustomAttributeArgument attributeArgument)
Expand Down Expand Up @@ -558,7 +564,7 @@ static void AddMethodToTypeDeclaration(CodeTypeDeclaration typeDeclaration, Meth
ReturnType = returnType,
};
PopulateCustomAttributes(member.MethodReturnType, method.ReturnTypeCustomAttributes, excludeAttributes);
PopulateGenericParameters(member, method.TypeParameters);
PopulateGenericParameters(member, method.TypeParameters, excludeAttributes);
PopulateMethodParameters(member, method.Parameters, excludeAttributes, IsExtensionMethod(member));

typeDeclaration.Members.Add(method);
Expand Down
22 changes: 22 additions & 0 deletions src/PublicApiGeneratorTests/Class_generics.cs
Expand Up @@ -19,6 +19,19 @@ public class ClassWithGenericType<T>
}");
}

[Fact]
public void Should_output_generic_type_parameter_attribute()
{
AssertPublicApi(typeof(ClassWithGenericTypeAttribute<>),
@"namespace PublicApiGeneratorTests.Examples
{
public class ClassWithGenericTypeAttribute<[PublicApiGeneratorTests.Examples.MyTypeAttribute()] T>
{
public ClassWithGenericTypeAttribute() { }
}
}");
}

[Fact]
public void Should_output_multiple_generic_type_parameters()
{
Expand Down Expand Up @@ -141,6 +154,15 @@ public class ClassWithGenericType<T>
{
}

public class ClassWithGenericTypeAttribute<[MyType]T>
{
}

[AttributeUsage(AttributeTargets.GenericParameter)]
public sealed class MyTypeAttribute : Attribute
{
}

public class ClassWithMultipleGenericTypes<T1, T2>
{
}
Expand Down

0 comments on commit 56aa60b

Please sign in to comment.