Skip to content
Merged
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
1 change: 1 addition & 0 deletions mdoc/Consts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,6 @@ public static class Consts
public const string InAttribute = "System.Runtime.InteropServices.InAttribute";
public const string TupleElementNamesAttribute = "System.Runtime.CompilerServices.TupleElementNamesAttribute";
public const string IsExternalInit = "System.Runtime.CompilerServices.IsExternalInit";
public const string NativeIntegerAttribute = "System.Runtime.CompilerServices.NativeIntegerAttribute";
}
}
39 changes: 30 additions & 9 deletions mdoc/Mono.Documentation/Updater/AttributeParserContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ public class AttributeParserContext : IAttributeParserContext
private int nullableAttributeIndex;
private int dynamicAttributeIndex;
private int tupleNameAttributeIndex;
private int nativeIntegerAttributeIndex;
private ICustomAttributeProvider provider;
private ReadOnlyCollection<bool?> nullableAttributeFlags;
private ReadOnlyCollection<bool> dynamicAttributeFlags;
private string[] tupleElementNames;
private bool[] nativeIntegerFlags;

private AttributeParserContext(ICustomAttributeProvider provider)
{
Expand All @@ -24,6 +26,7 @@ private AttributeParserContext(ICustomAttributeProvider provider)
ReadDynamicAttribute();
ReadNullableAttribute();
ReadTupleElementNames();
ReadNativeIntegerAttribute();
}

private bool ExistsNullableAttribute
Expand Down Expand Up @@ -82,6 +85,11 @@ public string GetTupleElementName()
return (tupleElementNames == null || tupleNameAttributeIndex >= tupleElementNames.Length) ? null : tupleElementNames[tupleNameAttributeIndex++];
}

public bool IsNativeInteger()
{
return nativeIntegerFlags != null && nativeIntegerAttributeIndex < nativeIntegerFlags.Length && nativeIntegerFlags[nativeIntegerAttributeIndex++];
}

private void ReadDynamicAttribute()
{
DynamicTypeProvider dynamicTypeProvider = new DynamicTypeProvider(provider);
Expand All @@ -100,15 +108,28 @@ private void ReadNullableAttribute()

private void ReadTupleElementNames()
{
if (provider != null && provider.HasCustomAttributes)
{
var tupleNamesAttr = provider.CustomAttributes.Where(attr => attr.AttributeType.FullName == Consts.TupleElementNamesAttribute).FirstOrDefault();
if (tupleNamesAttr != null)
{
var constructorArgs = tupleNamesAttr.ConstructorArguments.FirstOrDefault().Value as CustomAttributeArgument[];
tupleElementNames = constructorArgs?.Select(arg => arg.Value as string).ToArray();
}
}
tupleElementNames = ReadCustomAttributeValue<string>(Consts.TupleElementNamesAttribute);
}

private void ReadNativeIntegerAttribute()
{
nativeIntegerFlags = ReadCustomAttributeValue<bool>(
Consts.NativeIntegerAttribute,
() => new bool[] { true });
}

private T[] ReadCustomAttributeValue<T>(string attributeName, Func<T[]> init = null)
{
if (provider == null || !provider.HasCustomAttributes) return null;

var customAttribute = provider.CustomAttributes.Where(attr => attr.AttributeType.FullName == attributeName).FirstOrDefault();

if (customAttribute == null) return null;

if (!customAttribute.HasConstructorArguments) return init?.Invoke();

var constructorArgs = customAttribute.ConstructorArguments[0].Value as CustomAttributeArgument[];
return constructorArgs?.Select(arg => (T)arg.Value).ToArray();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,10 @@ public string GetTupleElementName()
{
return null;
}

public bool IsNativeInteger()
{
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,6 @@ public virtual bool TryGetAttributeString(CustomAttribute attribute, out string
return false;
}

TypeDefinition attrType = attribute.AttributeType as TypeDefinition;
if (attrType != null && !DocUtils.IsPublic(attrType)
|| (FormatterManager.SlashdocFormatter.GetName(attribute.AttributeType) == null)
|| Array.IndexOf(IgnorableAttributes, attribute.AttributeType.FullName) >= 0)
{
rval = null;
return false;
}

var fields = new List<string>();

for (int i = 0; i < attribute.ConstructorArguments.Count; ++i)
Expand Down Expand Up @@ -127,11 +118,34 @@ public virtual string MakeAttributesValueString(object argumentValue, TypeRefere

private bool IsIgnoredAttribute(CustomAttribute customAttribute)
{
var attrType = customAttribute.AttributeType;

if (attrType == null) return true;

// An Obsolete attribute with a known string is added to all ref-like structs
// https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/span-safety.md#metadata-representation-or-ref-like-structs
return customAttribute.AttributeType.FullName == typeof(ObsoleteAttribute).FullName
if (attrType.FullName == typeof(ObsoleteAttribute).FullName
&& customAttribute.HasConstructorArguments
&& customAttribute.ConstructorArguments.First().Value.ToString() == Consts.RefTypeObsoleteString;
&& customAttribute.ConstructorArguments.First().Value.ToString() == Consts.RefTypeObsoleteString)
{
return true;
}

// Expose this attribute in ECMAXML to let ECMA2YML pick up
// https://ceapex.visualstudio.com/Engineering/_workitems/edit/550401
if (attrType.FullName == Consts.NativeIntegerAttribute)
{
return false;
}

var attrTypeDef = attrType as TypeDefinition;
if (attrTypeDef != null && !DocUtils.IsPublic(attrTypeDef) || (FormatterManager.SlashdocFormatter.GetName(attrType) == null)
|| Array.IndexOf(IgnorableAttributes, attrType.FullName) >= 0)
{
return true;
}

return false;
}

// FIXME: get TypeReferences instead of string comparison?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ public class CSharpFullMemberFormatter : MemberFormatter
public CSharpFullMemberFormatter() : this(null) {}
public CSharpFullMemberFormatter(TypeMap map) : base(map) { }

private static readonly Dictionary<string, string> NativeIntTypeMap = new Dictionary<string, string>()
{
{ "System.IntPtr", "nint" },
{ "System.UIntPtr", "nuint" },
};

public override string Language
{
get { return "C#"; }
Expand All @@ -25,7 +31,7 @@ protected override StringBuilder AppendNamespace (StringBuilder buf, TypeReferen
return buf;
}

protected virtual string GetCSharpType (string t)
protected virtual string GetCSharpType(string t)
{
// make sure there are no modifiers in the type string (add them back before returning)
string typeToCompare = t;
Expand Down Expand Up @@ -83,6 +89,11 @@ protected override StringBuilder AppendTypeName (StringBuilder buf, TypeReferenc
return base.AppendTypeName (buf, type, context);
}

if (NativeIntTypeMap.TryGetValue(t, out string typeName) && context.IsNativeInteger())
{
return buf.Append(typeName);
}

string s = GetCSharpType (t);
if (s != null)
{
Expand Down Expand Up @@ -152,7 +163,7 @@ protected override StringBuilder AppendSpecialGenericNullableValueTypeName (Stri
genArgTypeList.Add (underlyingTypeName);
}
var genArgList = genInst.GenericArguments.Select((_, index) => string.Format("{0}{1}", genArgTypeList[index], genArgNameList[index] == null ? String.Empty : (" " + genArgNameList[index])));
buf.Append (string.Join (",", genArgList));
buf.Append (string.Join (", ", genArgList));
buf.Append (")");
return buf;
}
Expand Down
1 change: 1 addition & 0 deletions mdoc/Mono.Documentation/Updater/IAttributeParserContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ public interface IAttributeParserContext
bool IsDynamic();
bool IsNullable();
string GetTupleElementName();
bool IsNativeInteger();
}
}
31 changes: 25 additions & 6 deletions mdoc/mdoc.Test/FormatterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ public void CSharpTuple()
var member = GetMethod(typeof(NullablesAndTuples), m => m.Name == "TupleReturn");
var formatter = new CSharpFullMemberFormatter();
var sig = formatter.GetDeclaration(member);
Assert.AreEqual("public (int,string) TupleReturn ();", sig);
Assert.AreEqual("public (int, string) TupleReturn ();", sig);
}

[Test]
Expand Down Expand Up @@ -425,27 +425,27 @@ public void CSharpTupleNamesTypeTest()
{
var type = GetType(typeof(SampleClasses.TupleNamesTestClass<,>));
var typeSignature = formatter.GetDeclaration(type);
Assert.AreEqual("public class TupleNamesTestClass<T1,T2> : IComparable<(T1,T2)>", typeSignature);
Assert.AreEqual("public class TupleNamesTestClass<T1,T2> : IComparable<(T1, T2)>", typeSignature);
}

[Test]
public void CSharpTupleNamesPropertyTest()
{
var property = GetProperty(typeof(SampleClasses.TupleNamesTestClass<,>), m => m.Name == "TuplePropertyType");
var propertySignature = formatter.GetDeclaration(property);
Assert.AreEqual("public (int a,int b) TuplePropertyType { get; }", propertySignature);
Assert.AreEqual("public (int a, int b) TuplePropertyType { get; }", propertySignature);
}

[Test]
public void CSharpTupleNamesFieldTest()
{
var field = GetField(GetType(typeof(SampleClasses.TupleNamesTestClass<,>)), "TupleField");
var fieldSignature = formatter.GetDeclaration(field);
Assert.AreEqual("public (int a,int b,int c) TupleField;", fieldSignature);
Assert.AreEqual("public (int a, int b, int c) TupleField;", fieldSignature);
}

[TestCase("TupleMethod", "public (int a,int,int b) TupleMethod ((int,int) t1, (int b,int c,int d) t2, (int,int) t3);")]
[TestCase("RecursiveTupleMethod", "public ((int a,long b) c,int d) RecursiveTupleMethod ((((int a,long) b,string c) d,(int e,(float f,float g) h) i,int j) t);")]
[TestCase("TupleMethod", "public (int a, int, int b) TupleMethod ((int, int) t1, (int b, int c, int d) t2, (int, int) t3);")]
[TestCase("RecursiveTupleMethod", "public ((int a, long b) c, int d) RecursiveTupleMethod ((((int a, long) b, string c) d, (int e, (float f, float g) h) i, int j) t);")]
public void CSharpTupleNamesMethodTest(string methodName, string expectedSignature)
{
var method = GetMethod(typeof(SampleClasses.TupleNamesTestClass<,>), m => m.Name == methodName);
Expand All @@ -464,6 +464,25 @@ public void CSharpInitOnlySetterTest(string propertyName, string expectedSignatu
Assert.AreEqual(expectedSignature, propertySignature);
}

[Test]
public void CSharpNativeIntGenericTypeTest()
{
var type = GetType(typeof(SampleClasses.GenericNativeIntClass<>));
var typeSignature = formatter.GetDeclaration(type);
Assert.AreEqual("public class GenericNativeIntClass<nint>", typeSignature);
}

[TestCase("Method1", "public (nint, nuint) Method1 (nint a, nuint b, IntPtr c, UIntPtr d);")]
[TestCase("Method2", "public (nint, nuint) Method2 (List<nint> a, Dictionary<int,nuint> b);")]
[TestCase("Method3", "public (nint, nuint) Method3 ((nint, nuint) a, (nuint, IntPtr) b, (UIntPtr, string) c);")]
[TestCase("Method4", "public (((nint a, IntPtr) b, UIntPtr c) d, (nint e, (nuint f, IntPtr g) h) i) Method4 ();")]
public void CSharpNativeIntMethodTest(string methodName, string expectedSignature)
{
var method = GetMethod(typeof(SampleClasses.NativeIntClass), m => m.Name == methodName);
var methodSignature = formatter.GetDeclaration(method);
Assert.AreEqual(expectedSignature, methodSignature);
}

#region Helper Methods
string RealTypeName(string name){
switch (name) {
Expand Down
12 changes: 10 additions & 2 deletions mdoc/mdoc.Test/MDocUpdaterTests.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml;
using mdoc.Test.SampleClasses;
using Mono.Cecil;
using Mono.Cecil.Rocks;
using Mono.Collections.Generic;
using Mono.Documentation;
using Mono.Documentation.Updater;
Expand All @@ -31,6 +29,16 @@ public void Test_GetCustomAttributes_IgnoredObsoleteAttribute()
Assert.IsFalse(formatter.TryGetAttributeString(attributes.First(), out string rval));
}

[Test]
public void Test_GetCustomAttributes_EmitNativeIntegerAttribute()
{
var method = GetMethod(typeof(SampleClasses.NativeIntClass), "Method1");
static CustomAttribute GetNativeIntegerAttr(ParameterDefinition p) => p?.CustomAttributes.Where(attr => attr.AttributeType.FullName == Consts.NativeIntegerAttribute).FirstOrDefault();
Assert.IsNotNull(GetNativeIntegerAttr(method.Parameters[0]));
Assert.IsTrue(formatter.TryGetAttributeString(GetNativeIntegerAttr(method.Parameters[0]), out string rval));
Assert.IsNull(GetNativeIntegerAttr(method.Parameters[2]));
}

[Test]
public void Test_GetDocParameterType_CppGenericParameterType_ReturnsTypeWithGenericParameters()
{
Expand Down
16 changes: 8 additions & 8 deletions mdoc/mdoc.Test/NullableReferenceTypesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ public class NullableReferenceTypesTests : BasicFormatterTests<CSharpMemberForma
[TestCase("Tuple<int,int>?", "NullableTupleOfValueType")]
[TestCase("Tuple<int?,int?>", "TupleOfNullableValueType")]
[TestCase("Tuple<int?,int?>?", "NullableTupleOfNullableValueType")]
[TestCase("(int,int)", "ValueTupleOfValueType")]
[TestCase("(int,int)?", "NullableValueTupleOfValueType")]
[TestCase("(int?,int?)", "ValueTupleOfNullableValueType")]
[TestCase("(int?,int?)?", "NullableValueTupleOfNullableValueType")]
[TestCase("(int, int)", "ValueTupleOfValueType")]
[TestCase("(int, int)?", "NullableValueTupleOfValueType")]
[TestCase("(int?, int?)", "ValueTupleOfNullableValueType")]
[TestCase("(int?, int?)?", "NullableValueTupleOfNullableValueType")]
[TestCase("ICollection<int>", "InterfaceOfValueType")]
[TestCase("ICollection<int>?", "NullableInterfaceOfValueType")]
[TestCase("ICollection<int?>?", "NullableInterfaceOfNullableValueType")]
Expand Down Expand Up @@ -83,10 +83,10 @@ public class NullableReferenceTypesTests : BasicFormatterTests<CSharpMemberForma
[TestCase("Tuple<string,string>?", "NullableTupleOfReferenceType")]
[TestCase("Tuple<string?,string?>", "TupleOfNullableReferenceType")]
[TestCase("Tuple<string?,string?>?", "NullableTupleOfNullableReferenceType")]
[TestCase("(string,string)", "ValueTupleOfReferenceType")]
[TestCase("(string,string)?", "NullableValueTupleOfReferenceType")]
[TestCase("(string?,string?)", "ValueTupleOfNullableReferenceType")]
[TestCase("(string?,string?)?", "NullableValueTupleOfNullableReferenceType")]
[TestCase("(string, string)", "ValueTupleOfReferenceType")]
[TestCase("(string, string)?", "NullableValueTupleOfReferenceType")]
[TestCase("(string?, string?)", "ValueTupleOfNullableReferenceType")]
[TestCase("(string?, string?)?", "NullableValueTupleOfNullableReferenceType")]
[TestCase("ICollection<string>", "InterfaceOfReferenceType")]
[TestCase("ICollection<string>?", "NullableInterfaceOfReferenceType")]
[TestCase("ICollection<string?>?", "NullableInterfaceOfNullableReferenceType")]
Expand Down
30 changes: 30 additions & 0 deletions mdoc/mdoc.Test/SampleClasses/NativeIntClass.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;

namespace mdoc.Test.SampleClasses
{
public class NativeIntClass
{
public (nint, nuint) Method1(nint a, nuint b, IntPtr c, UIntPtr d)
{
return (a + c, b + d);
}

public (nint, nuint) Method2(List<nint> a, Dictionary<int, nuint> b)
{
return (a[0], b[0]);
}

public (nint, nuint) Method3((nint, nuint) a, (nuint, IntPtr) b, (UIntPtr, string) c)
{
return (a.Item1 + b.Item2, b.Item1 + c.Item1);
}

public (((nint a, IntPtr) b, UIntPtr c) d, (nint e, (nuint f, IntPtr g) h) i) Method4() => throw null;
}

public class GenericNativeIntClass<nint>
{

}
}