Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add nullable feature support to the ReturnType and Parameter element for generating XML #567

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 29 additions & 6 deletions mdoc/Mono.Documentation/MDocUpdater.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3946,7 +3946,7 @@ public void MakeParameters (XmlElement root, MemberReference member, IList<Param
return new
{
Name = p.Name,
Type = GetDocParameterType(p.ParameterType),
Type = GetDocParameterType(p.ParameterType, AttributeParserContext.Create(p)),
Index = i,
IsOut = p.IsOut,
IsIn = p.IsIn,
Expand Down Expand Up @@ -4210,20 +4210,31 @@ private void MakeParameters (XmlElement root, MemberReference mi, FrameworkTypeE

public static string GetDocParameterType (TypeReference type, bool useTypeProjection = false)
{
var typename = GetDocTypeFullName (type, useTypeProjection).Replace ("@", "&");
return GetDocParameterType (type, EmptyAttributeParserContext.Empty (), useTypeProjection);
}

public static string GetDocParameterType (TypeReference type, IAttributeParserContext context, bool useTypeProjection = false)
{
var isNullable = context.IsNullable ();
var typename = GetDocTypeFullName (type, context, useTypeProjection).Replace ("@", "&");

if (useTypeProjection || string.IsNullOrEmpty(typename))
{
typename = MDocUpdater.Instance.TypeMap?.GetTypeName("C#", typename) ?? typename;
}


if (isNullable)
typename += DocUtils.GetTypeNullableSymbol (type, isNullable);

return typename;
}

private void MakeReturnValue (FrameworkTypeEntry typeEntry, XmlElement root, TypeReference type, MemberReference member, IList<CustomAttribute> attributes, bool shouldDuplicateWithNew = false)
{
XmlElement e = WriteElement (root, "ReturnValue");
var valueToUse = GetDocTypeFullName (type, false);
var context = AttributeParserContext.Create ((ICustomAttributeProvider)member);
var isNullable = context.IsNullable ();
var valueToUse = GetDocTypeFullName (type, context, false);
if ((type.IsRequiredModifier && ((RequiredModifierType)type).ElementType.IsByReference)
|| type.IsByReference)
e.SetAttribute("RefType", "Ref");
Expand All @@ -4235,6 +4246,8 @@ private void MakeReturnValue (FrameworkTypeEntry typeEntry, XmlElement root, Typ
valueToUse = valueToUse.Remove(valueToUse.Length - 1);
}

if (isNullable)
valueToUse += DocUtils.GetTypeNullableSymbol (type, isNullable);

DocUtils.AddElementWithFx(
typeEntry,
Expand Down Expand Up @@ -4422,12 +4435,22 @@ internal static string GetMemberType (MemberReference mi)

private static string GetDocTypeName (TypeReference type, bool useTypeProjection = true)
{
return docTypeFormatter.GetName (type, useTypeProjection: useTypeProjection);
return GetDocTypeName (type, EmptyAttributeParserContext.Empty (), useTypeProjection);
}

private static string GetDocTypeName (TypeReference type, IAttributeParserContext context, bool useTypeProjection = true)
{
return docTypeFormatter.GetName (type, context, useTypeProjection: useTypeProjection);
}

internal static string GetDocTypeFullName (TypeReference type, bool useTypeProjection = true, bool isTypeofOperator = false)
{
return DocTypeFullMemberFormatter.Default.GetName (type, useTypeProjection: useTypeProjection, isTypeofOperator: isTypeofOperator);
return GetDocTypeFullName (type, EmptyAttributeParserContext.Empty (), useTypeProjection, isTypeofOperator);
}

internal static string GetDocTypeFullName (TypeReference type, IAttributeParserContext context, bool useTypeProjection = true, bool isTypeofOperator = false)
{
return DocTypeFullMemberFormatter.Default.GetName (type, context, useTypeProjection: useTypeProjection, isTypeofOperator: isTypeofOperator);
}

internal static string GetXPathForMember (DocumentationMember member)
Expand Down
25 changes: 25 additions & 0 deletions mdoc/Mono.Documentation/Updater/DocUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -945,5 +945,30 @@ public static TypeDefinition FixUnnamedParameters(TypeDefinition type)

return type;
}

public static string GetTypeNullableSymbol(TypeReference type, bool? isNullableType)
{
if (isNullableType.IsTrue() && !IsValueTypeOrDefineByReference(type) && !type.FullName.Equals("System.Void"))
{
return "?";
}

return string.Empty;
}

private static bool IsValueTypeOrDefineByReference(TypeReference type)
{
if (type.IsValueType)
{
return true;
}

if (type is ByReferenceType byRefType)
{
return byRefType.ElementType.IsValueType;
}

return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,12 @@ protected override string GetTypeName (TypeReference type, IAttributeParserConte
return AppendSpecialGenericNullableValueTypeName (new StringBuilder (), genType, context, appendGeneric, useTypeProjection).ToString ();
}

return base.GetTypeName (type, context, appendGeneric, useTypeProjection);
return base.GetTypeName (type, context, appendGeneric, useTypeProjection, isTypeofOperator);
}

protected override bool IsSpecialGenericNullableValueType (GenericInstanceType genInst)
{
return genInst != null && (genInst.Name.StartsWith("ValueTuple`") ||
(genInst.Name.StartsWith("Nullable`") && genInst.HasGenericArguments));
return genInst != null && genInst.HasGenericArguments && (genInst.Name.StartsWith("ValueTuple`") || genInst.Name.StartsWith("Nullable`"));
}

protected override StringBuilder AppendSpecialGenericNullableValueTypeName (StringBuilder buf, GenericInstanceType genInst, IAttributeParserContext context, bool appendGeneric = true, bool useTypeProjection = true)
Expand Down Expand Up @@ -431,27 +430,7 @@ protected override StringBuilder AppendMethodName (StringBuilder buf, MethodDefi

protected override string GetTypeNullableSymbol(TypeReference type, bool? isNullableType)
{
if (isNullableType.IsTrue() && !IsValueTypeOrDefineByReference(type) && !type.FullName.Equals("System.Void"))
{
return "?";
}

return string.Empty;
}

private bool IsValueTypeOrDefineByReference(TypeReference type)
{
if (type.IsValueType)
{
return true;
}

if (type is ByReferenceType byRefType)
{
return byRefType.ElementType.IsValueType;
}

return false;
return DocUtils.GetTypeNullableSymbol(type, isNullableType);
}

protected override StringBuilder AppendGenericMethodConstraints (StringBuilder buf, MethodDefinition method)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
namespace Mono.Documentation.Updater
using Mono.Cecil;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Mono.Documentation.Updater
{
class DocTypeFullMemberFormatter : MemberFormatter
public class DocTypeFullMemberFormatter : MemberFormatter
{
private static MemberFormatter defaultFormatter;
public static MemberFormatter Default
Expand All @@ -20,5 +25,60 @@ protected override string NestedTypeSeparator
{
get { return "+"; }
}

protected override string GetTypeNullableSymbol(TypeReference type, bool? isNullableType)
{
return DocUtils.GetTypeNullableSymbol(type, isNullableType);
}

protected override string GetTypeName(TypeReference type, IAttributeParserContext context, bool appendGeneric = true, bool useTypeProjection = true, bool isTypeofOperator = false)
{
GenericInstanceType genType = type as GenericInstanceType;
if (IsSpecialGenericNullableValueType(genType))
{
return AppendSpecialGenericNullableValueTypeName(new StringBuilder(), genType, context, appendGeneric, useTypeProjection).ToString();
}

return base.GetTypeName(type, context, appendGeneric, useTypeProjection, isTypeofOperator);
}

protected override bool IsSpecialGenericNullableValueType(GenericInstanceType genInst)
{
return genInst != null && genInst.HasGenericArguments && (genInst.Name.StartsWith("ValueTuple`") || genInst.Name.StartsWith("Nullable`"));
}

protected override StringBuilder AppendSpecialGenericNullableValueTypeName(StringBuilder buf, GenericInstanceType genInst, IAttributeParserContext context, bool appendGeneric = true, bool useTypeProjection = true)
{
if (genInst.Name.StartsWith("Nullable`") && genInst.HasGenericArguments)
{
var underlyingTypeName = GetTypeName(genInst.GenericArguments.First(), context, appendGeneric, useTypeProjection);
buf.Append($"System.Nullable<{underlyingTypeName}>");

return buf;
}

if (genInst.Name.StartsWith("ValueTuple`"))
{
buf.Append("System.ValueTuple<");
var genArgList = new List<string>();
foreach (var item in genInst.GenericArguments)
{
var isNullableType = false;
if (!item.IsValueType)
{
isNullableType = context.IsNullable();
}

var underlyingTypeName = GetTypeName(item, context, appendGeneric, useTypeProjection) + GetTypeNullableSymbol(item, isNullableType);
genArgList.Add(underlyingTypeName);
}
buf.Append(string.Join(",", genArgList));
buf.Append(">");

return buf;
}

return buf;
}
}
}
6 changes: 3 additions & 3 deletions mdoc/Mono.Documentation/Updater/Formatters/MemberFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public virtual string GetName (MemberReference member, IAttributeParserContext c
(member == null ? "null" : member.GetType ().ToString ()));
}

protected virtual string GetTypeName (TypeReference type, bool appendGeneric = true, bool useTypeProjection = true)
protected string GetTypeName (TypeReference type, bool appendGeneric = true, bool useTypeProjection = true)
{
return GetTypeName (type, EmptyAttributeParserContext.Empty(), appendGeneric, useTypeProjection: useTypeProjection);
}
Expand Down Expand Up @@ -210,13 +210,13 @@ protected StringBuilder _AppendTypeName (StringBuilder buf, TypeReference type,

protected virtual bool IsSpecialGenericNullableValueType(GenericInstanceType genInst)
{
// For special C# nullable value type only, the CSharpFullMemberFormatter subclass will override the method.
// For special C# nullable value type only, the subclass CSharpFullMemberFormatter, and DocTypeFullMemberFormatter will override the method.
return false;
}

protected virtual StringBuilder AppendSpecialGenericNullableValueTypeName(StringBuilder buf, GenericInstanceType genInst, IAttributeParserContext context, bool appendGeneric = true, bool useTypeProjection = true)
{
// For special C# nullable value type only, the CSharpFullMemberFormatter subclass will override the method.
// For special C# nullable value type only, the subclass CSharpFullMemberFormatter, and DocTypeFullMemberFormatter will override the method.
return buf;
}

Expand Down
23 changes: 23 additions & 0 deletions mdoc/Mono.Documentation/Updater/NullableReferenceTypeProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@ private ICollection<ICustomAttributeProvider> GetTypeNullableAttributes()
return GetTypeNullableAttributes(fieldDefinition);
}

if (provider is MethodDefinition methodDefinition)
{
return GetTypeNullableAttributes(methodDefinition);
}

if (provider is EventDefinition eventDefinition)
{
return GetTypeNullableAttributes(eventDefinition);
}

throw new ArgumentException("We don't support this custom attribute provider type now.", nameof(provider));
}

Expand Down Expand Up @@ -144,13 +154,26 @@ private ICollection<ICustomAttributeProvider> GetTypeNullableAttributes(FieldDef
};
}

private ICollection<ICustomAttributeProvider> GetTypeNullableAttributes(EventDefinition eventDefinition)
{
return GetTypeNullableAttributes(eventDefinition.AddMethod.Parameters[0]);
}

private ICollection<ICustomAttributeProvider> GetTypeNullableAttributes(ICustomAttributeProvider customAttributeProvider, MethodDefinition methodDefinition)
{
var resultList = new List<ICustomAttributeProvider>
{
customAttributeProvider
};

resultList.AddRange(GetTypeNullableAttributes(methodDefinition));

return resultList;
}

private ICollection<ICustomAttributeProvider> GetTypeNullableAttributes(MethodDefinition methodDefinition)
{
var resultList = new List<ICustomAttributeProvider>();
if (methodDefinition != null)
{
resultList.Add(methodDefinition);
Expand Down
80 changes: 80 additions & 0 deletions mdoc/mdoc.Test/DocTypeNullableReferenceTypesTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using Mono.Cecil;
using Mono.Documentation.Updater;
using NUnit.Framework;

namespace mdoc.Test
{
public class DocTypeNullableReferenceTypesTests : BasicFormatterTests<MemberFormatter>
{
private const string NullableReferenceTypesAssemblyPath = "../../../../external/Test/mdoc.Test.NullableReferenceTypes.dll";

protected override MemberFormatter formatter => DocTypeFullMemberFormatter.Default;

[TestCase("System.Int32", "ValueType")]
[TestCase("System.Nullable<System.Int32>", "NullableValueType")]
[TestCase("System.ValueTuple<System.Int32,System.Int32>", "ValueTupleOfValueType")]
[TestCase("System.Nullable<System.ValueTuple<System.Int32,System.Int32>>", "NullableValueTupleOfValueType")]
[TestCase("System.Collections.Generic.Dictionary<System.String,System.Collections.Generic.Dictionary<System.String,System.String>>", "DictionaryOfReferenceTypeKeyAndDictionaryOfReferenceTypeValue")]
[TestCase("System.Collections.Generic.Dictionary<System.String?,System.Collections.Generic.Dictionary<System.String?,System.String?>?>?", "NullableDictionaryOfNullableReferenceTypeKeyAndNullableDictionaryOfNullableReferenceTypeValue")]
public void DocTypeReturnType(string returnType, string methodName)
{
// This is a common test for the return type of method, extension method, property, field, and operator overloading.
// They have the same process logic that we just test once the type of them.
var type = GetType(NullableReferenceTypesAssemblyPath, "mdoc.Test.NullableReferenceTypes.CommonType");
var method = GetMethod(type, i => i.Name == methodName);

var typeName = GetDocTypeName(method.MethodReturnType, method.ReturnType);

Assert.AreEqual(returnType, typeName);
}

[TestCase("System.EventHandler", "EventHandler")]
[TestCase("System.EventHandler<System.EventArgs>?", "NullableGenericEventHandler")]
[TestCase("System.EventHandler<System.EventArgs?>?", "NullableGenericEventHandlerOfNullableEventArgs")]
public void DocTypeEventType(string returnType, string methodName)
{
var type = GetType(NullableReferenceTypesAssemblyPath, "mdoc.Test.NullableReferenceTypes.Event");
var @event = GetEvent(type, methodName) as EventDefinition;

var typeName = GetDocTypeName(@event, @event.EventType);

Assert.AreEqual(returnType, typeName);
}

[TestCase("NullableAndNonNullableValueType", "System.Int32", "System.Nullable<System.Int32>", "System.Int32")]
[TestCase("NullableAndNonNullableReferenceType", "System.String", "System.String?", "System.String")]
[TestCase("NullableAndNonNullableInterfaceOfValueType", "System.Collections.Generic.ICollection<System.Int32>", "System.Collections.Generic.ICollection<System.Int32>?", "System.Collections.Generic.ICollection<System.Int32>")]
[TestCase("NullableAndNonNullableInterfaceOfReferenceType", "System.Collections.Generic.ICollection<System.String>", "System.Collections.Generic.ICollection<System.String>?", "System.Collections.Generic.ICollection<System.String>")]
public void DocTypeParameterType(string methodName, params string[] methodParameterType)
{
// This is a common test for the parameter type of constructor, method, extension method, delegate and operator overloading.
// They have the same process logic that we just test once the type of them.
var type = GetType(NullableReferenceTypesAssemblyPath, "mdoc.Test.NullableReferenceTypes.MethodParameter");
var method = GetMethod(type, i => i.Name == methodName);

for (int i = 0; i < method.Parameters.Count; i++)
{
var methodParameter = method.Parameters[i];
var expectedParameterType = methodParameterType[i];

var typeName = GetDocTypeName(methodParameter, methodParameter.ParameterType);

Assert.AreEqual(expectedParameterType, typeName);
}
}

private string GetDocTypeName(ICustomAttributeProvider provider, TypeReference type)
{
var context = AttributeParserContext.Create(provider);
var isNullable = context.IsNullable();
var typeName = DocTypeFullMemberFormatter.Default.GetName(type, context, useTypeProjection: false);

if (isNullable)
{
typeName += DocUtils.GetTypeNullableSymbol(type, isNullable);
}

return typeName;
}
}
}
1 change: 1 addition & 0 deletions mdoc/mdoc.Test/mdoc.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
<Compile Include="BasicTests.cs" />
<Compile Include="CppWinRtMembersTests.cs" />
<Compile Include="CppWinRtFormatterTests.cs" />
<Compile Include="DocTypeNullableReferenceTypesTests.cs" />
<Compile Include="Enumeration\AttachedEntityTests.cs" />
<Compile Include="DotnetCoreAssemblyResolver.cs" />
<Compile Include="FrameworkIndexHelperTests.cs" />
Expand Down