Support .NET language feature: function pointers (#623)

* Support .NET language feature: function pointers

* update

* update

* add test cases

* add test dll

* update

* Use delegate * for other languages

* update

* Fix new member added for function pointers

* update
This commit is contained in:
Min Huang 2022-04-18 09:55:57 +08:00 коммит произвёл GitHub
Родитель b5f62fcd16
Коммит 93806a9b54
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
15 изменённых файлов: 196 добавлений и 18 удалений

Двоичные данные
external/Test/FunctionPointersTest.dll поставляемый Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -49,8 +49,10 @@ namespace Mono.Documentation
public const string IsByRefLikeAttribute = "System.Runtime.CompilerServices.IsByRefLikeAttribute";
public const string IsReadOnlyAttribute = "System.Runtime.CompilerServices.IsReadOnlyAttribute";
public const string InAttribute = "System.Runtime.InteropServices.InAttribute";
public const string OutAttribute = "System.Runtime.InteropServices.OutAttribute";
public const string TupleElementNamesAttribute = "System.Runtime.CompilerServices.TupleElementNamesAttribute";
public const string IsExternalInit = "System.Runtime.CompilerServices.IsExternalInit";
public const string NativeIntegerAttribute = "System.Runtime.CompilerServices.NativeIntegerAttribute";
public const string CallConvPrefix = "System.Runtime.CompilerServices.CallConv";
}
}

Просмотреть файл

@ -153,6 +153,9 @@ namespace Mono.Documentation.Updater
string xmlMemberType = member.Parameters[i];
// After we support function pointers, "method" as type should be skipped and not be compared with current function pointer type.
if (xmlMemberType == "method") continue;
// TODO: take into account extension method reftype
bool xmlIsRefType = xmlMemberType.Contains ('&');
bool refTypesMatch = isRefType == xmlIsRefType;

Просмотреть файл

@ -1,6 +1,7 @@
using Mono.Cecil;
using Mono.Documentation.Util;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@ -591,43 +592,51 @@ namespace Mono.Documentation.Updater.Formatters
{
if (DocUtils.IsExtensionMethod (method))
buf.Append ("this ");
AppendParameter (buf, parameters[0]);
AppendParameter(buf, parameters[0]);
for (int i = 1; i < parameters.Count; ++i)
{
buf.Append (", ");
AppendParameter (buf, parameters[i]);
buf.Append(", ");
AppendParameter(buf, parameters[i]);
}
}
return buf.Append (end);
}
private StringBuilder AppendParameter (StringBuilder buf, ParameterDefinition parameter)
protected override StringBuilder AppendParameter(StringBuilder buf, ParameterDefinition parameter)
{
TypeReference parameterType = parameter.ParameterType;
var refType = new BitArray(3);
if (parameterType is RequiredModifierType requiredModifierType)
{
switch(requiredModifierType.ModifierType.FullName)
{
case Consts.InAttribute: refType.Set(0, true); break;
case Consts.OutAttribute: refType.Set(1, true); break;
default: break;
}
parameterType = requiredModifierType.ElementType;
}
if (parameterType is ByReferenceType byReferenceType)
{
if (parameter.IsOut)
{
buf.Append ("out ");
refType.Set(1, true);
}
else if(parameter.IsIn && DocUtils.HasCustomAttribute(parameter, Consts.IsReadOnlyAttribute))
{
buf.Append("in ");
refType.Set(0, true);
}
else
{
buf.Append("ref ");
refType.Set(2, true);
}
parameterType = byReferenceType.ElementType;
}
buf.Append(refType.Get(0) ? "in " : (refType.Get(1) ? "out " : (refType.Get(2) ? "ref ": "")));
if (parameter.HasCustomAttributes)
{
var isParams = parameter.CustomAttributes.Any (ca => ca.AttributeType.Name == "ParamArrayAttribute");
@ -639,8 +648,7 @@ namespace Mono.Documentation.Updater.Formatters
var isNullableType = context.IsNullable ();
buf.Append (GetTypeName (parameterType, context));
buf.Append (GetTypeNullableSymbol (parameter.ParameterType, isNullableType));
buf.Append (" ");
buf.Append (parameter.Name);
buf.Append (string.IsNullOrEmpty(parameter.Name) ? "" : " " + parameter.Name);
if (parameter.HasDefault && parameter.IsOptional && parameter.HasConstant)
{

Просмотреть файл

@ -715,7 +715,7 @@ namespace Mono.Documentation.Updater.Formatters.CppFormatters
return buf.Append (end);
}
protected virtual StringBuilder AppendParameter (StringBuilder buf, ParameterDefinition parameter)
protected override StringBuilder AppendParameter (StringBuilder buf, ParameterDefinition parameter)
{
if (parameter.ParameterType is ByReferenceType)
{

Просмотреть файл

@ -1,4 +1,7 @@
namespace Mono.Documentation.Updater
using Mono.Cecil;
using System.Text;
namespace Mono.Documentation.Updater
{
class DocTypeFullMemberFormatter : MemberFormatter
{
@ -20,5 +23,10 @@
{
get { return "+"; }
}
protected override StringBuilder AppendParameter(StringBuilder buf, ParameterDefinition parameterDef)
{
return buf.Append(GetName(parameterDef.ParameterType, useTypeProjection: false, isTypeofOperator: false));
}
}
}

Просмотреть файл

@ -692,7 +692,7 @@ namespace Mono.Documentation.Updater
return buf;
}
private void AppendParameter(StringBuilder buf, ParameterDefinition parameter)
protected override StringBuilder AppendParameter(StringBuilder buf, ParameterDefinition parameter)
{
bool isFSharpFunction = IsFSharpFunction(parameter.ParameterType);
if (isFSharpFunction)
@ -701,6 +701,7 @@ namespace Mono.Documentation.Updater
buf.Append(typeName);
if (isFSharpFunction)
buf.Append(")");
return buf;
}
protected override string GetPropertyDeclaration(PropertyDefinition property)

Просмотреть файл

@ -440,7 +440,7 @@ namespace Mono.Documentation.Updater.Formatters
return buf.Append (end);
}
private StringBuilder AppendParameter (StringBuilder buf, ParameterDefinition parameter)
protected override StringBuilder AppendParameter (StringBuilder buf, ParameterDefinition parameter)
{
if (parameter.ParameterType is ByReferenceType)
{
@ -599,5 +599,10 @@ namespace Mono.Documentation.Updater.Formatters
return buf.ToString ();
}
protected override void AppendFunctionPointerTypeName(StringBuilder buf, FunctionPointerType type, IAttributeParserContext context)
{
buf.Append("method");
}
}
}

Просмотреть файл

@ -136,6 +136,11 @@ namespace Mono.Documentation.Updater.Formatters
return buf.Append(string.Join(", ", parameters.Select(i => i.Name)));
}
protected override StringBuilder AppendParameter(StringBuilder buf, ParameterDefinition parameter)
{
return buf.Append(parameter.Name);
}
protected MethodDefinition GetConstructor(TypeDefinition type)
{
return type.GetConstructors()

Просмотреть файл

@ -1,4 +1,5 @@
using Mono.Cecil;
using Mono.Documentation.Updater.Formatters;
using Mono.Documentation.Util;
using System;
using System.Collections.Generic;
@ -131,6 +132,69 @@ namespace Mono.Documentation.Updater
return AppendArrayModifiers (buf, (ArrayType)type);
}
protected virtual void AppendFunctionPointerTypeName(StringBuilder buf, FunctionPointerType type, IAttributeParserContext context)
{
buf.Append("delegate*");
var callingConvention = GetCallingConvention(type);
if (callingConvention != MethodCallingConvention.Default.ToString())
{
buf.Append(" unmanaged");
if (!string.IsNullOrEmpty(callingConvention))
{
buf.Append("[").Append(callingConvention).Append("]");
}
}
buf.Append("<");
if (type.Parameters?.Count > 0)
{
for (int i = 0; i < type.Parameters.Count; i++)
{
AppendParameter(buf, type.Parameters[i]);
buf.Append(", ");
}
}
AppendReturnTypeName(buf, type, true);
buf.Append(">");
}
private string GetCallingConvention(FunctionPointerType type)
{
var callingConvention = type.CallingConvention.ToString("D");
// Cecil lib uses "9" to stands for "Unmanaged Ext"
if (callingConvention != "9")
{
return NormalizeCallingConvention(type.CallingConvention);
}
else
{
StringBuilder buf = new StringBuilder();
AssembleCallingConvention(type.ReturnType, buf);
return buf.ToString();
}
}
private string NormalizeCallingConvention(MethodCallingConvention callingConvention)
{
if (callingConvention == MethodCallingConvention.C) return "Cdecl";
var callConv = callingConvention.ToString().ToLower();
return char.ToUpper(callConv[0]) + callConv.Substring(1);
}
private void AssembleCallingConvention(TypeReference type, StringBuilder buf)
{
if (!(type is OptionalModifierType optionalModifierType)) return;
var modifier = optionalModifierType.ModifierType.FullName;
if (modifier.StartsWith(Consts.CallConvPrefix))
{
if (!string.IsNullOrEmpty(buf.ToString())) buf.Append(", ");
buf.Append(modifier.Substring(Consts.CallConvPrefix.Length));
AssembleCallingConvention(optionalModifierType.ElementType, buf);
}
}
protected virtual bool ShouldStripModFromTypeName
{
get => true;
@ -168,6 +232,11 @@ namespace Mono.Documentation.Updater
AppendPointerTypeName (interimBuilder, type, context);
return SetBuffer(buf, interimBuilder, useTypeProjection: useTypeProjection);
}
if (type is FunctionPointerType functionPointerType)
{
AppendFunctionPointerTypeName(interimBuilder, functionPointerType, context);
return SetBuffer(buf, interimBuilder, useTypeProjection: useTypeProjection);
}
if (type is GenericParameter)
{
AppendTypeName (interimBuilder, type, context);
@ -562,15 +631,14 @@ namespace Mono.Documentation.Updater
}
private StringBuilder AppendReturnTypeName (StringBuilder buf, MethodDefinition method)
protected StringBuilder AppendReturnTypeName (StringBuilder buf, IMethodSignature method, bool noTrailingSpace = false)
{
var context = AttributeParserContext.Create (method.MethodReturnType);
var isNullableType = context.IsNullable ();
var returnTypeName = GetTypeName (method.ReturnType, context);
buf.Append (returnTypeName);
buf.Append (GetTypeNullableSymbol (method.ReturnType, isNullableType));
buf.Append (" ");
buf.Append (noTrailingSpace ? "" : " ");
return buf;
}
@ -604,6 +672,11 @@ namespace Mono.Documentation.Updater
return buf;
}
protected virtual StringBuilder AppendParameter(StringBuilder buf, ParameterDefinition parameterDef)
{
return buf;
}
protected virtual StringBuilder AppendGenericMethodConstraints (StringBuilder buf, MethodDefinition method)
{
return buf;

Просмотреть файл

@ -562,7 +562,7 @@ namespace Mono.Documentation.Updater
return buf.Append(end);
}
private StringBuilder AppendParameter(StringBuilder buf, ParameterDefinition parameter)
protected override StringBuilder AppendParameter(StringBuilder buf, ParameterDefinition parameter)
{
if (parameter.IsOptional)
{

Просмотреть файл

@ -483,6 +483,35 @@ namespace mdoc.Test
Assert.AreEqual(expectedSignature, methodSignature);
}
[TestCase("UnsafeCombine", "public static R UnsafeCombine<T1,T2,R> (delegate*<T1, T2, R> combinator, T1 left, T2 right);")]
[TestCase("UnsafeCombine1", "public static R UnsafeCombine1<T1,T2,R> (delegate* unmanaged[Cdecl]<T1, T2, R> combinator, T1 left, T2 right);")]
[TestCase("UnsafeCombine2", "public static R UnsafeCombine2<T1,T2,T3,R> (delegate* unmanaged[Stdcall]<ref T1, in T2, out T3, R> combinator, T1 left, T2 right, T3 outVar);")]
[TestCase("UnsafeCombine3", "public static R UnsafeCombine3<T1,T2,R> (delegate* unmanaged[Fastcall]<T1, T2, ref R> combinator, T1 left, T2 right);")]
[TestCase("UnsafeCombine4", "public static R UnsafeCombine4<T1,T2,R> (delegate* unmanaged[Thiscall]<T1, T2, ref readonly R> combinator, T1 left, T2 right);")]
[TestCase("UnsafeCombine5", "public static void UnsafeCombine5 (delegate* unmanaged[Cdecl]<void> combinator);")]
[TestCase("UnsafeCombine6", "public static void UnsafeCombine6 (delegate*<delegate* unmanaged[Fastcall]<string, int>, delegate*<string, int>> combinator);")]
[TestCase("UnsafeCombine7", "public static delegate*<delegate* unmanaged[Thiscall]<string, int>, delegate*<string, int>> UnsafeCombine7 ();")]
public void CSharpFuctionPointersTest(string methodName, string expectedSignature)
{
var method = GetMethod(typeof(SampleClasses.FunctionPointers), m => m.Name == methodName);
var methodSignature = formatter.GetDeclaration(method);
Assert.AreEqual(expectedSignature, methodSignature);
}
[TestCase("UnsafeCombine1", "public static R UnsafeCombine1<T1,T2,R> (delegate* unmanaged<T1, T2, R> combinator, T1 left, T2 right);")]
[TestCase("UnsafeCombine2", "public static R UnsafeCombine2<T1,T2,R> (delegate* unmanaged[Cdecl, SuppressGCTransition]<T1, T2, R> combinator, T1 left, T2 right);")]
[TestCase("UnsafeCombine3", "public static R UnsafeCombine3<T1,T2,R> (delegate* unmanaged[Stdcall, MemberFunction]<T1, T2, R> combinator, T1 left, T2 right);")]
[TestCase("UnsafeCombine4", "public static void UnsafeCombine4 (delegate*<delegate* unmanaged[Cdecl, Fastcall]<string, int>, delegate*<string, int>> combinator);")]
[TestCase("UnsafeCombine5", "public static delegate* unmanaged[Cdecl, Fastcall]<delegate* unmanaged[Thiscall, MemberFunction]<string, int>, delegate*<string, int>> UnsafeCombine5 ();")]
public void CSharpFuctionPointersUnmanagedExtTest(string methodName, string expectedSignature)
{
var functionPointersDllPath = "../../../../external/Test/FunctionPointersTest.dll";
var type = GetType(functionPointersDllPath, "FunctionPointersTest.FunctionPointers");
var method = GetMethod(type, m => m.Name == methodName);
var methodSignature = formatter.GetDeclaration(method);
Assert.AreEqual(expectedSignature, methodSignature);
}
#region Helper Methods
string RealTypeName(string name){
switch (name) {

Просмотреть файл

@ -48,6 +48,18 @@ namespace mdoc.Test
Assert.AreEqual("Mono_DocTest_Generic.GenericBase<U>", parameterType);
}
[TestCase("UnsafeCombine", "delegate*<T1, T2, R>")]
[TestCase("UnsafeCombineOverload", "delegate*<System.IntPtr, System.UIntPtr, R>")]
public void Test_GetDocParameterType_CSharpFunctionPointer(string methodName, string expected)
{
var method = GetMethod(typeof(SampleClasses.FunctionPointers), methodName);
string parameterType = MDocUpdater.GetDocParameterType(method.Parameters[0].ParameterType);
Assert.AreEqual(expected, parameterType);
}
[Test]
public void Test_GetNamespace_IgnoredNamespaceGeneric()
{

Просмотреть файл

@ -0,0 +1,31 @@
using System;
namespace mdoc.Test.SampleClasses
{
public class FunctionPointers
{
public unsafe static R UnsafeCombine<T1, T2, R>(delegate*<T1, T2, R> combinator, T1 left, T2 right) =>
combinator(left, right);
public unsafe static R UnsafeCombineOverload<R>(delegate*<IntPtr, UIntPtr, R> combinator, IntPtr left, UIntPtr right) =>
combinator(left, right);
public unsafe static R UnsafeCombine1<T1, T2, R>(delegate* unmanaged[Cdecl]<T1, T2, R> combinator, T1 left, T2 right) =>
combinator(left, right);
public unsafe static R UnsafeCombine2<T1, T2, T3, R>(delegate* unmanaged[Stdcall]<ref T1, in T2, out T3, R> combinator, T1 left, T2 right, T3 outVar) =>
combinator(ref left, right, out outVar);
public unsafe static R UnsafeCombine3<T1, T2, R>(delegate* unmanaged[Fastcall]<T1, T2, ref R> combinator, T1 left, T2 right) =>
combinator(left, right);
public unsafe static R UnsafeCombine4<T1, T2, R>(delegate* unmanaged[Thiscall]<T1, T2, ref readonly R> combinator, T1 left, T2 right) =>
combinator(left, right);
public unsafe static void UnsafeCombine5(delegate* unmanaged[Cdecl]<void> combinator) => combinator();
public unsafe static void UnsafeCombine6(delegate*<delegate* unmanaged[Fastcall]<string, int>, delegate*<string, int>> combinator) => combinator(null);
public unsafe static delegate*<delegate* unmanaged[Thiscall]<string, int>, delegate*<string, int>> UnsafeCombine7() => throw null;
}
}

Просмотреть файл

@ -16,6 +16,7 @@
</PropertyGroup>
<PropertyGroup>
<RunPostBuildEvent>Always</RunPostBuildEvent>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Reference Include="mdoc.Test.Cplusplus, Version=1.0.6709.28740, Culture=neutral, processorArchitecture=x86">