diff --git a/mdoc/Mono.Documentation/MDocUpdater.cs b/mdoc/Mono.Documentation/MDocUpdater.cs
index 9dcaebb1f..e27949198 100644
--- a/mdoc/Mono.Documentation/MDocUpdater.cs
+++ b/mdoc/Mono.Documentation/MDocUpdater.cs
@@ -3946,7 +3946,7 @@ public void MakeParameters (XmlElement root, MemberReference member, IList 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");
@@ -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,
@@ -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)
diff --git a/mdoc/Mono.Documentation/Updater/DocUtils.cs b/mdoc/Mono.Documentation/Updater/DocUtils.cs
index 8a29d0104..7a6c67d46 100644
--- a/mdoc/Mono.Documentation/Updater/DocUtils.cs
+++ b/mdoc/Mono.Documentation/Updater/DocUtils.cs
@@ -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;
+ }
}
}
diff --git a/mdoc/Mono.Documentation/Updater/Formatters/CSharpFullMemberFormatter.cs b/mdoc/Mono.Documentation/Updater/Formatters/CSharpFullMemberFormatter.cs
index 7d0aa7c8f..295d66bcb 100644
--- a/mdoc/Mono.Documentation/Updater/Formatters/CSharpFullMemberFormatter.cs
+++ b/mdoc/Mono.Documentation/Updater/Formatters/CSharpFullMemberFormatter.cs
@@ -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)
@@ -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)
diff --git a/mdoc/Mono.Documentation/Updater/Formatters/DocTypeFullMemberFormatter.cs b/mdoc/Mono.Documentation/Updater/Formatters/DocTypeFullMemberFormatter.cs
index 5c370b5d9..55c1d2a7c 100644
--- a/mdoc/Mono.Documentation/Updater/Formatters/DocTypeFullMemberFormatter.cs
+++ b/mdoc/Mono.Documentation/Updater/Formatters/DocTypeFullMemberFormatter.cs
@@ -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
@@ -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();
+ 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;
+ }
}
}
\ No newline at end of file
diff --git a/mdoc/Mono.Documentation/Updater/Formatters/MemberFormatter.cs b/mdoc/Mono.Documentation/Updater/Formatters/MemberFormatter.cs
index d5f634e10..4612b3dbc 100644
--- a/mdoc/Mono.Documentation/Updater/Formatters/MemberFormatter.cs
+++ b/mdoc/Mono.Documentation/Updater/Formatters/MemberFormatter.cs
@@ -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);
}
@@ -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;
}
diff --git a/mdoc/Mono.Documentation/Updater/NullableReferenceTypeProvider.cs b/mdoc/Mono.Documentation/Updater/NullableReferenceTypeProvider.cs
index 8832a2a0f..8216464f3 100644
--- a/mdoc/Mono.Documentation/Updater/NullableReferenceTypeProvider.cs
+++ b/mdoc/Mono.Documentation/Updater/NullableReferenceTypeProvider.cs
@@ -117,6 +117,16 @@ private ICollection 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));
}
@@ -144,6 +154,11 @@ private ICollection GetTypeNullableAttributes(FieldDef
};
}
+ private ICollection GetTypeNullableAttributes(EventDefinition eventDefinition)
+ {
+ return GetTypeNullableAttributes(eventDefinition.AddMethod.Parameters[0]);
+ }
+
private ICollection GetTypeNullableAttributes(ICustomAttributeProvider customAttributeProvider, MethodDefinition methodDefinition)
{
var resultList = new List
@@ -151,6 +166,14 @@ private ICollection GetTypeNullableAttributes(ICustomA
customAttributeProvider
};
+ resultList.AddRange(GetTypeNullableAttributes(methodDefinition));
+
+ return resultList;
+ }
+
+ private ICollection GetTypeNullableAttributes(MethodDefinition methodDefinition)
+ {
+ var resultList = new List();
if (methodDefinition != null)
{
resultList.Add(methodDefinition);
diff --git a/mdoc/mdoc.Test/DocTypeNullableReferenceTypesTests.cs b/mdoc/mdoc.Test/DocTypeNullableReferenceTypesTests.cs
new file mode 100644
index 000000000..98a0c126e
--- /dev/null
+++ b/mdoc/mdoc.Test/DocTypeNullableReferenceTypesTests.cs
@@ -0,0 +1,80 @@
+using Mono.Cecil;
+using Mono.Documentation.Updater;
+using NUnit.Framework;
+
+namespace mdoc.Test
+{
+ public class DocTypeNullableReferenceTypesTests : BasicFormatterTests
+ {
+ private const string NullableReferenceTypesAssemblyPath = "../../../../external/Test/mdoc.Test.NullableReferenceTypes.dll";
+
+ protected override MemberFormatter formatter => DocTypeFullMemberFormatter.Default;
+
+ [TestCase("System.Int32", "ValueType")]
+ [TestCase("System.Nullable", "NullableValueType")]
+ [TestCase("System.ValueTuple", "ValueTupleOfValueType")]
+ [TestCase("System.Nullable>", "NullableValueTupleOfValueType")]
+ [TestCase("System.Collections.Generic.Dictionary>", "DictionaryOfReferenceTypeKeyAndDictionaryOfReferenceTypeValue")]
+ [TestCase("System.Collections.Generic.Dictionary?>?", "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?", "NullableGenericEventHandler")]
+ [TestCase("System.EventHandler?", "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")]
+ [TestCase("NullableAndNonNullableReferenceType", "System.String", "System.String?", "System.String")]
+ [TestCase("NullableAndNonNullableInterfaceOfValueType", "System.Collections.Generic.ICollection", "System.Collections.Generic.ICollection?", "System.Collections.Generic.ICollection")]
+ [TestCase("NullableAndNonNullableInterfaceOfReferenceType", "System.Collections.Generic.ICollection", "System.Collections.Generic.ICollection?", "System.Collections.Generic.ICollection")]
+ 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;
+ }
+ }
+}
diff --git a/mdoc/mdoc.Test/mdoc.Test.csproj b/mdoc/mdoc.Test/mdoc.Test.csproj
index 6f33aeb64..c32fb8447 100644
--- a/mdoc/mdoc.Test/mdoc.Test.csproj
+++ b/mdoc/mdoc.Test/mdoc.Test.csproj
@@ -77,6 +77,7 @@
+