Skip to content

Commit b24bc75

Browse files
authored
feat#550401:Support C# 9 nint/nuint (#620)
* Support C# 9 nint/nuint * update * update * update * update * update * update * update * update * fix test cases * fix test cases * fix integration test * add unit test
1 parent 4cf39bd commit b24bc75

10 files changed

Lines changed: 148 additions & 38 deletions

File tree

mdoc/Consts.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,6 @@ public static class Consts
5151
public const string InAttribute = "System.Runtime.InteropServices.InAttribute";
5252
public const string TupleElementNamesAttribute = "System.Runtime.CompilerServices.TupleElementNamesAttribute";
5353
public const string IsExternalInit = "System.Runtime.CompilerServices.IsExternalInit";
54+
public const string NativeIntegerAttribute = "System.Runtime.CompilerServices.NativeIntegerAttribute";
5455
}
5556
}

mdoc/Mono.Documentation/Updater/AttributeParserContext.cs

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ public class AttributeParserContext : IAttributeParserContext
1212
private int nullableAttributeIndex;
1313
private int dynamicAttributeIndex;
1414
private int tupleNameAttributeIndex;
15+
private int nativeIntegerAttributeIndex;
1516
private ICustomAttributeProvider provider;
1617
private ReadOnlyCollection<bool?> nullableAttributeFlags;
1718
private ReadOnlyCollection<bool> dynamicAttributeFlags;
1819
private string[] tupleElementNames;
20+
private bool[] nativeIntegerFlags;
1921

2022
private AttributeParserContext(ICustomAttributeProvider provider)
2123
{
@@ -24,6 +26,7 @@ private AttributeParserContext(ICustomAttributeProvider provider)
2426
ReadDynamicAttribute();
2527
ReadNullableAttribute();
2628
ReadTupleElementNames();
29+
ReadNativeIntegerAttribute();
2730
}
2831

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

88+
public bool IsNativeInteger()
89+
{
90+
return nativeIntegerFlags != null && nativeIntegerAttributeIndex < nativeIntegerFlags.Length && nativeIntegerFlags[nativeIntegerAttributeIndex++];
91+
}
92+
8593
private void ReadDynamicAttribute()
8694
{
8795
DynamicTypeProvider dynamicTypeProvider = new DynamicTypeProvider(provider);
@@ -100,15 +108,28 @@ private void ReadNullableAttribute()
100108

101109
private void ReadTupleElementNames()
102110
{
103-
if (provider != null && provider.HasCustomAttributes)
104-
{
105-
var tupleNamesAttr = provider.CustomAttributes.Where(attr => attr.AttributeType.FullName == Consts.TupleElementNamesAttribute).FirstOrDefault();
106-
if (tupleNamesAttr != null)
107-
{
108-
var constructorArgs = tupleNamesAttr.ConstructorArguments.FirstOrDefault().Value as CustomAttributeArgument[];
109-
tupleElementNames = constructorArgs?.Select(arg => arg.Value as string).ToArray();
110-
}
111-
}
111+
tupleElementNames = ReadCustomAttributeValue<string>(Consts.TupleElementNamesAttribute);
112+
}
113+
114+
private void ReadNativeIntegerAttribute()
115+
{
116+
nativeIntegerFlags = ReadCustomAttributeValue<bool>(
117+
Consts.NativeIntegerAttribute,
118+
() => new bool[] { true });
119+
}
120+
121+
private T[] ReadCustomAttributeValue<T>(string attributeName, Func<T[]> init = null)
122+
{
123+
if (provider == null || !provider.HasCustomAttributes) return null;
124+
125+
var customAttribute = provider.CustomAttributes.Where(attr => attr.AttributeType.FullName == attributeName).FirstOrDefault();
126+
127+
if (customAttribute == null) return null;
128+
129+
if (!customAttribute.HasConstructorArguments) return init?.Invoke();
130+
131+
var constructorArgs = customAttribute.ConstructorArguments[0].Value as CustomAttributeArgument[];
132+
return constructorArgs?.Select(arg => (T)arg.Value).ToArray();
112133
}
113134
}
114135
}

mdoc/Mono.Documentation/Updater/EmptyAttributeParserContext.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,10 @@ public string GetTupleElementName()
3131
{
3232
return null;
3333
}
34+
35+
public bool IsNativeInteger()
36+
{
37+
return false;
38+
}
3439
}
3540
}

mdoc/Mono.Documentation/Updater/Formatters/AttributeFormatters/AttributeFormatter.cs

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,6 @@ public virtual bool TryGetAttributeString(CustomAttribute attribute, out string
7777
return false;
7878
}
7979

80-
TypeDefinition attrType = attribute.AttributeType as TypeDefinition;
81-
if (attrType != null && !DocUtils.IsPublic(attrType)
82-
|| (FormatterManager.SlashdocFormatter.GetName(attribute.AttributeType) == null)
83-
|| Array.IndexOf(IgnorableAttributes, attribute.AttributeType.FullName) >= 0)
84-
{
85-
rval = null;
86-
return false;
87-
}
88-
8980
var fields = new List<string>();
9081

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

128119
private bool IsIgnoredAttribute(CustomAttribute customAttribute)
129120
{
121+
var attrType = customAttribute.AttributeType;
122+
123+
if (attrType == null) return true;
124+
130125
// An Obsolete attribute with a known string is added to all ref-like structs
131126
// https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/span-safety.md#metadata-representation-or-ref-like-structs
132-
return customAttribute.AttributeType.FullName == typeof(ObsoleteAttribute).FullName
127+
if (attrType.FullName == typeof(ObsoleteAttribute).FullName
133128
&& customAttribute.HasConstructorArguments
134-
&& customAttribute.ConstructorArguments.First().Value.ToString() == Consts.RefTypeObsoleteString;
129+
&& customAttribute.ConstructorArguments.First().Value.ToString() == Consts.RefTypeObsoleteString)
130+
{
131+
return true;
132+
}
133+
134+
// Expose this attribute in ECMAXML to let ECMA2YML pick up
135+
// https://ceapex.visualstudio.com/Engineering/_workitems/edit/550401
136+
if (attrType.FullName == Consts.NativeIntegerAttribute)
137+
{
138+
return false;
139+
}
140+
141+
var attrTypeDef = attrType as TypeDefinition;
142+
if (attrTypeDef != null && !DocUtils.IsPublic(attrTypeDef) || (FormatterManager.SlashdocFormatter.GetName(attrType) == null)
143+
|| Array.IndexOf(IgnorableAttributes, attrType.FullName) >= 0)
144+
{
145+
return true;
146+
}
147+
148+
return false;
135149
}
136150

137151
// FIXME: get TypeReferences instead of string comparison?

mdoc/Mono.Documentation/Updater/Formatters/CSharpFullMemberFormatter.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ public class CSharpFullMemberFormatter : MemberFormatter
1212
public CSharpFullMemberFormatter() : this(null) {}
1313
public CSharpFullMemberFormatter(TypeMap map) : base(map) { }
1414

15+
private static readonly Dictionary<string, string> NativeIntTypeMap = new Dictionary<string, string>()
16+
{
17+
{ "System.IntPtr", "nint" },
18+
{ "System.UIntPtr", "nuint" },
19+
};
20+
1521
public override string Language
1622
{
1723
get { return "C#"; }
@@ -25,7 +31,7 @@ protected override StringBuilder AppendNamespace (StringBuilder buf, TypeReferen
2531
return buf;
2632
}
2733

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

92+
if (NativeIntTypeMap.TryGetValue(t, out string typeName) && context.IsNativeInteger())
93+
{
94+
return buf.Append(typeName);
95+
}
96+
8697
string s = GetCSharpType (t);
8798
if (s != null)
8899
{
@@ -152,7 +163,7 @@ protected override StringBuilder AppendSpecialGenericNullableValueTypeName (Stri
152163
genArgTypeList.Add (underlyingTypeName);
153164
}
154165
var genArgList = genInst.GenericArguments.Select((_, index) => string.Format("{0}{1}", genArgTypeList[index], genArgNameList[index] == null ? String.Empty : (" " + genArgNameList[index])));
155-
buf.Append (string.Join (",", genArgList));
166+
buf.Append (string.Join (", ", genArgList));
156167
buf.Append (")");
157168
return buf;
158169
}

mdoc/Mono.Documentation/Updater/IAttributeParserContext.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ public interface IAttributeParserContext
66
bool IsDynamic();
77
bool IsNullable();
88
string GetTupleElementName();
9+
bool IsNativeInteger();
910
}
1011
}

mdoc/mdoc.Test/FormatterTests.cs

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ public void CSharpTuple()
297297
var member = GetMethod(typeof(NullablesAndTuples), m => m.Name == "TupleReturn");
298298
var formatter = new CSharpFullMemberFormatter();
299299
var sig = formatter.GetDeclaration(member);
300-
Assert.AreEqual("public (int,string) TupleReturn ();", sig);
300+
Assert.AreEqual("public (int, string) TupleReturn ();", sig);
301301
}
302302

303303
[Test]
@@ -425,27 +425,27 @@ public void CSharpTupleNamesTypeTest()
425425
{
426426
var type = GetType(typeof(SampleClasses.TupleNamesTestClass<,>));
427427
var typeSignature = formatter.GetDeclaration(type);
428-
Assert.AreEqual("public class TupleNamesTestClass<T1,T2> : IComparable<(T1,T2)>", typeSignature);
428+
Assert.AreEqual("public class TupleNamesTestClass<T1,T2> : IComparable<(T1, T2)>", typeSignature);
429429
}
430430

431431
[Test]
432432
public void CSharpTupleNamesPropertyTest()
433433
{
434434
var property = GetProperty(typeof(SampleClasses.TupleNamesTestClass<,>), m => m.Name == "TuplePropertyType");
435435
var propertySignature = formatter.GetDeclaration(property);
436-
Assert.AreEqual("public (int a,int b) TuplePropertyType { get; }", propertySignature);
436+
Assert.AreEqual("public (int a, int b) TuplePropertyType { get; }", propertySignature);
437437
}
438438

439439
[Test]
440440
public void CSharpTupleNamesFieldTest()
441441
{
442442
var field = GetField(GetType(typeof(SampleClasses.TupleNamesTestClass<,>)), "TupleField");
443443
var fieldSignature = formatter.GetDeclaration(field);
444-
Assert.AreEqual("public (int a,int b,int c) TupleField;", fieldSignature);
444+
Assert.AreEqual("public (int a, int b, int c) TupleField;", fieldSignature);
445445
}
446446

447-
[TestCase("TupleMethod", "public (int a,int,int b) TupleMethod ((int,int) t1, (int b,int c,int d) t2, (int,int) t3);")]
448-
[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);")]
447+
[TestCase("TupleMethod", "public (int a, int, int b) TupleMethod ((int, int) t1, (int b, int c, int d) t2, (int, int) t3);")]
448+
[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);")]
449449
public void CSharpTupleNamesMethodTest(string methodName, string expectedSignature)
450450
{
451451
var method = GetMethod(typeof(SampleClasses.TupleNamesTestClass<,>), m => m.Name == methodName);
@@ -464,6 +464,25 @@ public void CSharpInitOnlySetterTest(string propertyName, string expectedSignatu
464464
Assert.AreEqual(expectedSignature, propertySignature);
465465
}
466466

467+
[Test]
468+
public void CSharpNativeIntGenericTypeTest()
469+
{
470+
var type = GetType(typeof(SampleClasses.GenericNativeIntClass<>));
471+
var typeSignature = formatter.GetDeclaration(type);
472+
Assert.AreEqual("public class GenericNativeIntClass<nint>", typeSignature);
473+
}
474+
475+
[TestCase("Method1", "public (nint, nuint) Method1 (nint a, nuint b, IntPtr c, UIntPtr d);")]
476+
[TestCase("Method2", "public (nint, nuint) Method2 (List<nint> a, Dictionary<int,nuint> b);")]
477+
[TestCase("Method3", "public (nint, nuint) Method3 ((nint, nuint) a, (nuint, IntPtr) b, (UIntPtr, string) c);")]
478+
[TestCase("Method4", "public (((nint a, IntPtr) b, UIntPtr c) d, (nint e, (nuint f, IntPtr g) h) i) Method4 ();")]
479+
public void CSharpNativeIntMethodTest(string methodName, string expectedSignature)
480+
{
481+
var method = GetMethod(typeof(SampleClasses.NativeIntClass), m => m.Name == methodName);
482+
var methodSignature = formatter.GetDeclaration(method);
483+
Assert.AreEqual(expectedSignature, methodSignature);
484+
}
485+
467486
#region Helper Methods
468487
string RealTypeName(string name){
469488
switch (name) {

mdoc/mdoc.Test/MDocUpdaterTests.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
using System.Collections.Generic;
22
using System.IO;
33
using System.Linq;
4-
using System.Reflection;
54
using System.Xml;
65
using mdoc.Test.SampleClasses;
76
using Mono.Cecil;
8-
using Mono.Cecil.Rocks;
97
using Mono.Collections.Generic;
108
using Mono.Documentation;
119
using Mono.Documentation.Updater;
@@ -31,6 +29,16 @@ public void Test_GetCustomAttributes_IgnoredObsoleteAttribute()
3129
Assert.IsFalse(formatter.TryGetAttributeString(attributes.First(), out string rval));
3230
}
3331

32+
[Test]
33+
public void Test_GetCustomAttributes_EmitNativeIntegerAttribute()
34+
{
35+
var method = GetMethod(typeof(SampleClasses.NativeIntClass), "Method1");
36+
static CustomAttribute GetNativeIntegerAttr(ParameterDefinition p) => p?.CustomAttributes.Where(attr => attr.AttributeType.FullName == Consts.NativeIntegerAttribute).FirstOrDefault();
37+
Assert.IsNotNull(GetNativeIntegerAttr(method.Parameters[0]));
38+
Assert.IsTrue(formatter.TryGetAttributeString(GetNativeIntegerAttr(method.Parameters[0]), out string rval));
39+
Assert.IsNull(GetNativeIntegerAttr(method.Parameters[2]));
40+
}
41+
3442
[Test]
3543
public void Test_GetDocParameterType_CppGenericParameterType_ReturnsTypeWithGenericParameters()
3644
{

mdoc/mdoc.Test/NullableReferenceTypesTests.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ public class NullableReferenceTypesTests : BasicFormatterTests<CSharpMemberForma
3636
[TestCase("Tuple<int,int>?", "NullableTupleOfValueType")]
3737
[TestCase("Tuple<int?,int?>", "TupleOfNullableValueType")]
3838
[TestCase("Tuple<int?,int?>?", "NullableTupleOfNullableValueType")]
39-
[TestCase("(int,int)", "ValueTupleOfValueType")]
40-
[TestCase("(int,int)?", "NullableValueTupleOfValueType")]
41-
[TestCase("(int?,int?)", "ValueTupleOfNullableValueType")]
42-
[TestCase("(int?,int?)?", "NullableValueTupleOfNullableValueType")]
39+
[TestCase("(int, int)", "ValueTupleOfValueType")]
40+
[TestCase("(int, int)?", "NullableValueTupleOfValueType")]
41+
[TestCase("(int?, int?)", "ValueTupleOfNullableValueType")]
42+
[TestCase("(int?, int?)?", "NullableValueTupleOfNullableValueType")]
4343
[TestCase("ICollection<int>", "InterfaceOfValueType")]
4444
[TestCase("ICollection<int>?", "NullableInterfaceOfValueType")]
4545
[TestCase("ICollection<int?>?", "NullableInterfaceOfNullableValueType")]
@@ -83,10 +83,10 @@ public class NullableReferenceTypesTests : BasicFormatterTests<CSharpMemberForma
8383
[TestCase("Tuple<string,string>?", "NullableTupleOfReferenceType")]
8484
[TestCase("Tuple<string?,string?>", "TupleOfNullableReferenceType")]
8585
[TestCase("Tuple<string?,string?>?", "NullableTupleOfNullableReferenceType")]
86-
[TestCase("(string,string)", "ValueTupleOfReferenceType")]
87-
[TestCase("(string,string)?", "NullableValueTupleOfReferenceType")]
88-
[TestCase("(string?,string?)", "ValueTupleOfNullableReferenceType")]
89-
[TestCase("(string?,string?)?", "NullableValueTupleOfNullableReferenceType")]
86+
[TestCase("(string, string)", "ValueTupleOfReferenceType")]
87+
[TestCase("(string, string)?", "NullableValueTupleOfReferenceType")]
88+
[TestCase("(string?, string?)", "ValueTupleOfNullableReferenceType")]
89+
[TestCase("(string?, string?)?", "NullableValueTupleOfNullableReferenceType")]
9090
[TestCase("ICollection<string>", "InterfaceOfReferenceType")]
9191
[TestCase("ICollection<string>?", "NullableInterfaceOfReferenceType")]
9292
[TestCase("ICollection<string?>?", "NullableInterfaceOfNullableReferenceType")]
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace mdoc.Test.SampleClasses
5+
{
6+
public class NativeIntClass
7+
{
8+
public (nint, nuint) Method1(nint a, nuint b, IntPtr c, UIntPtr d)
9+
{
10+
return (a + c, b + d);
11+
}
12+
13+
public (nint, nuint) Method2(List<nint> a, Dictionary<int, nuint> b)
14+
{
15+
return (a[0], b[0]);
16+
}
17+
18+
public (nint, nuint) Method3((nint, nuint) a, (nuint, IntPtr) b, (UIntPtr, string) c)
19+
{
20+
return (a.Item1 + b.Item2, b.Item1 + c.Item1);
21+
}
22+
23+
public (((nint a, IntPtr) b, UIntPtr c) d, (nint e, (nuint f, IntPtr g) h) i) Method4() => throw null;
24+
}
25+
26+
public class GenericNativeIntClass<nint>
27+
{
28+
29+
}
30+
}

0 commit comments

Comments
 (0)