Skip to content

Commit 9a6fe5f

Browse files
authored
.NET7: Support static abstract/virtual modifiers (#654)
* Add a sample interface for static virtual * update * update * update * Support EII of static abstract members in interface * update * update * update * Support F# * update * Support VB/C++ CLI * update * update * update * update * update
1 parent bd84cee commit 9a6fe5f

13 files changed

Lines changed: 195 additions & 89 deletions
8 KB
Binary file not shown.

mdoc/Consts.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ public static class Consts
5454
public const string TupleElementNamesAttribute = "System.Runtime.CompilerServices.TupleElementNamesAttribute";
5555
public const string IsExternalInit = "System.Runtime.CompilerServices.IsExternalInit";
5656
public const string NativeIntegerAttribute = "System.Runtime.CompilerServices.NativeIntegerAttribute";
57+
public const string ScopedRefAttribute= "System.Runtime.CompilerServices.ScopedRefAttribute";
58+
public const string LifetimeAnnotationAttribute = "System.Runtime.CompilerServices.LifetimeAnnotationAttribute";
5759
public const string CallConvPrefix = "System.Runtime.CompilerServices.CallConv";
5860
}
5961
}

mdoc/Mono.Documentation/Updater/DocUtils.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,12 @@ public static IEnumerable<T> SafeCast<T> (this System.Collections.IEnumerable li
161161

162162
public static bool IsExplicitlyImplemented (MethodDefinition method)
163163
{
164-
return method != null && method.IsPrivate && method.IsFinal && method.IsVirtual;
164+
if (method == null || !method.IsPrivate)
165+
{
166+
return false;
167+
}
168+
// Support EII for static abstract members in interface
169+
return (method.IsFinal && method.IsVirtual) || (method.IsStatic && method.HasOverrides);
165170
}
166171

167172
public static string GetTypeDotMember (string name)
@@ -838,7 +843,7 @@ public static bool IsDestructor(MethodDefinition method)
838843

839844
public static bool IsOperator(MethodReference method)
840845
{
841-
return method.Name.StartsWith("op_", StringComparison.Ordinal);
846+
return method.Name.Split('.').Last().StartsWith("op_", StringComparison.Ordinal);
842847
}
843848

844849
public static bool DocIdCheck(XmlNode a, XmlElement b)

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

Lines changed: 45 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -380,79 +380,81 @@ protected override string GetMethodDeclaration (MethodDefinition method)
380380
return null;
381381
}
382382

383-
protected override StringBuilder AppendMethodName (StringBuilder buf, MethodDefinition method)
383+
protected override StringBuilder AppendMethodName(StringBuilder buf, MethodDefinition method)
384384
{
385-
if (DocUtils.IsExplicitlyImplemented (method))
385+
var methodName = method.Name;
386+
if (DocUtils.IsExplicitlyImplemented(method))
386387
{
387388
TypeReference iface;
388389
MethodReference ifaceMethod;
389-
DocUtils.GetInfoForExplicitlyImplementedMethod (method, out iface, out ifaceMethod);
390-
return buf.Append (new CSharpMemberFormatter (this.TypeMap).GetName (iface))
391-
.Append ('.')
392-
.Append (ifaceMethod.Name);
390+
DocUtils.GetInfoForExplicitlyImplementedMethod(method, out iface, out ifaceMethod);
391+
buf.Append(new CSharpMemberFormatter(this.TypeMap).GetName(iface)).Append('.');
392+
methodName = ifaceMethod.Name;
393393
}
394394

395-
if (method.Name.StartsWith ("op_", StringComparison.Ordinal))
395+
if (methodName.StartsWith("op_", StringComparison.Ordinal))
396396
{
397397
// this is an operator
398-
switch (method.Name)
398+
switch (methodName)
399399
{
400400
case "op_Implicit":
401401
case "op_Explicit":
402402
buf.Length--; // remove the last space, which assumes a member name is coming
403403
return buf;
404404
case "op_Addition":
405405
case "op_UnaryPlus":
406-
return buf.Append ("operator +");
406+
return buf.Append("operator +");
407407
case "op_Subtraction":
408408
case "op_UnaryNegation":
409-
return buf.Append ("operator -");
409+
return buf.Append("operator -");
410410
case "op_Division":
411-
return buf.Append ("operator /");
411+
return buf.Append("operator /");
412412
case "op_Multiply":
413-
return buf.Append ("operator *");
413+
return buf.Append("operator *");
414414
case "op_Modulus":
415-
return buf.Append ("operator %");
415+
return buf.Append("operator %");
416416
case "op_BitwiseAnd":
417-
return buf.Append ("operator &");
417+
return buf.Append("operator &");
418418
case "op_BitwiseOr":
419-
return buf.Append ("operator |");
419+
return buf.Append("operator |");
420420
case "op_ExclusiveOr":
421-
return buf.Append ("operator ^");
421+
return buf.Append("operator ^");
422422
case "op_LeftShift":
423-
return buf.Append ("operator <<");
423+
return buf.Append("operator <<");
424424
case "op_RightShift":
425-
return buf.Append ("operator >>");
425+
return buf.Append("operator >>");
426426
case "op_LogicalNot":
427-
return buf.Append ("operator !");
427+
return buf.Append("operator !");
428428
case "op_OnesComplement":
429-
return buf.Append ("operator ~");
429+
return buf.Append("operator ~");
430430
case "op_Decrement":
431-
return buf.Append ("operator --");
431+
return buf.Append("operator --");
432432
case "op_Increment":
433-
return buf.Append ("operator ++");
433+
return buf.Append("operator ++");
434434
case "op_True":
435-
return buf.Append ("operator true");
435+
return buf.Append("operator true");
436436
case "op_False":
437-
return buf.Append ("operator false");
437+
return buf.Append("operator false");
438438
case "op_Equality":
439-
return buf.Append ("operator ==");
439+
return buf.Append("operator ==");
440440
case "op_Inequality":
441-
return buf.Append ("operator !=");
441+
return buf.Append("operator !=");
442442
case "op_LessThan":
443-
return buf.Append ("operator <");
443+
return buf.Append("operator <");
444444
case "op_LessThanOrEqual":
445-
return buf.Append ("operator <=");
445+
return buf.Append("operator <=");
446446
case "op_GreaterThan":
447-
return buf.Append ("operator >");
447+
return buf.Append("operator >");
448448
case "op_GreaterThanOrEqual":
449-
return buf.Append ("operator >=");
449+
return buf.Append("operator >=");
450450
default:
451-
return base.AppendMethodName (buf, method);
451+
return buf.Append(methodName);
452452
}
453453
}
454454
else
455-
return base.AppendMethodName (buf, method);
455+
{
456+
return buf.Append(methodName);
457+
}
456458
}
457459

458460
protected override string GetTypeNullableSymbol(TypeReference type, bool? isNullableType)
@@ -519,14 +521,16 @@ protected override StringBuilder AppendModifiers (StringBuilder buf, MethodDefin
519521
if (method.IsStatic) modifiers += " static";
520522
if (method.IsVirtual && !method.IsAbstract)
521523
{
522-
if ((method.Attributes & MethodAttributes.NewSlot) != 0) modifiers += " virtual";
524+
if ((method.Attributes & MethodAttributes.NewSlot) != 0 || method.IsStatic) modifiers += " virtual";
523525
else modifiers += " override";
524526
}
525527
TypeDefinition declType = (TypeDefinition)method.DeclaringType;
526-
if (method.IsAbstract && !declType.IsInterface) modifiers += " abstract";
528+
if (method.IsAbstract && (!declType.IsInterface || method.IsStatic)) modifiers += " abstract";
527529
if (method.IsFinal) modifiers += " sealed";
528530
if (modifiers == " virtual sealed") modifiers = "";
529-
if (declType.IsValueType && DocUtils.HasCustomAttribute(method, Consts.IsReadOnlyAttribute))
531+
if (declType.IsValueType
532+
&& !(method.IsSpecialName && method.Name.StartsWith("get_")) // Property without set method is by defualt readonly.
533+
&& DocUtils.HasCustomAttribute(method, Consts.IsReadOnlyAttribute))
530534
{
531535
modifiers += buf.Length == 0 ? "readonly" : " readonly";
532536
}
@@ -541,7 +545,7 @@ protected override StringBuilder AppendModifiers (StringBuilder buf, MethodDefin
541545
break;
542546
}
543547

544-
return buf.Append (modifiers);
548+
return buf.Append (buf.Length == 0 ? modifiers.TrimStart() : modifiers);
545549
}
546550

547551
protected override StringBuilder AppendRefTypeName(StringBuilder buf, ByReferenceType type, IAttributeParserContext context)
@@ -610,7 +614,9 @@ protected override StringBuilder AppendParameter(StringBuilder buf, ParameterDef
610614

611615
if (parameter.HasCustomAttributes)
612616
{
613-
var isScoped = parameter.CustomAttributes.Any(ca => ca.AttributeType.Name == "LifetimeAnnotationAttribute");
617+
var isScoped = parameter.CustomAttributes.Any(
618+
ca => ca.AttributeType.FullName == Consts.ScopedRefAttribute
619+
|| ca.AttributeType.FullName == Consts.LifetimeAnnotationAttribute); // Workaround as complier in ci pipeline has delay for update.
614620
if (isScoped)
615621
buf.AppendFormat("scoped ");
616622
}
@@ -698,23 +704,8 @@ protected override string GetPropertyDeclaration (PropertyDefinition property)
698704
if (method == null)
699705
method = property.GetMethod;
700706

701-
string modifiers = String.Empty;
702-
if (method.IsStatic) modifiers += " static";
703-
if (method.IsVirtual && !method.IsAbstract)
704-
{
705-
if ((method.Attributes & MethodAttributes.NewSlot) != 0)
706-
modifiers += " virtual";
707-
else
708-
modifiers += " override";
709-
}
710-
TypeDefinition declDef = (TypeDefinition)method.DeclaringType;
711-
if (method.IsAbstract && !declDef.IsInterface)
712-
modifiers += " abstract";
713-
if (method.IsFinal)
714-
modifiers += " sealed";
715-
if (modifiers == " virtual sealed")
716-
modifiers = "";
717-
buf.Append (modifiers).Append (' ');
707+
AppendModifiers(buf, method);
708+
buf.Append(' ');
718709

719710
var context = AttributeParserContext.Create (property);
720711
var isNullableType = context.IsNullable ();

mdoc/Mono.Documentation/Updater/Formatters/CppFormatters/CppFullMemberFormatter.cs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -480,11 +480,19 @@ protected virtual StringBuilder AppendExplisitImplementationMethod(StringBuilder
480480

481481
protected override StringBuilder AppendMethodName(StringBuilder buf, MethodDefinition method)
482482
{
483-
if (!method.Name.StartsWith("op_", StringComparison.Ordinal))
484-
return base.AppendMethodName(buf, method);
483+
var methodName = method.Name;
484+
int dotIndex = methodName.LastIndexOf('.');
485+
if (dotIndex != -1)
486+
{
487+
buf.Append(methodName.Substring(0, dotIndex + 1));
488+
methodName = methodName.Substring(dotIndex + 1);
489+
}
490+
491+
if (!methodName.StartsWith("op_", StringComparison.Ordinal))
492+
return buf.Append(methodName);
485493

486494
// this is an operator
487-
switch (method.Name)
495+
switch (methodName)
488496
{
489497
case "op_Implicit":
490498
case "op_Explicit":
@@ -537,9 +545,8 @@ protected override StringBuilder AppendMethodName(StringBuilder buf, MethodDefin
537545
case "op_GreaterThanOrEqual":
538546
return buf.Append("operator >=");
539547
default:
540-
return base.AppendMethodName(buf, method);
548+
return buf.Append(methodName);
541549
}
542-
543550
}
544551

545552
protected override StringBuilder AppendGenericMethodConstraints (StringBuilder buf, MethodDefinition method)

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

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -537,7 +537,7 @@ private FSharpMethodKind GetMethodKind(MethodDefinition method)
537537

538538
private void AppendModuleMethod(StringBuilder buf, MethodDefinition method)
539539
{
540-
if (!IsOperator(method))
540+
if (!DocUtils.IsOperator(method))
541541
{
542542
buf.Append($"{GetModuleName(method.DeclaringType)}.");
543543
}
@@ -591,7 +591,7 @@ private void AppendMethodDeclarationEnding(StringBuilder buf, MethodDefinition m
591591

592592
protected override StringBuilder AppendMethodName(StringBuilder buf, MethodDefinition method)
593593
{
594-
if (IsOperator(method))
594+
if (DocUtils.IsOperator(method))
595595
{
596596
// this is an operator
597597
if (TryAppendOperatorName(buf, method))
@@ -874,11 +874,12 @@ private CustomAttribute GetCustomAttribute(Collection<CustomAttribute> customAtt
874874

875875
protected bool TryAppendOperatorName(StringBuilder buf, MethodDefinition method)
876876
{
877-
if (!IsOperator(method))
877+
if (!DocUtils.IsOperator(method))
878878
return false;
879-
if (operators.ContainsKey(method.Name))
879+
var methodName = method.Name.Split('.').Last();
880+
if (operators.ContainsKey(methodName))
880881
{
881-
buf.Append($"( {operators[method.Name]} )");
882+
buf.Append($"( {operators[methodName]} )");
882883
return true;
883884
}
884885

@@ -925,11 +926,6 @@ protected override StringBuilder AppendPointerTypeName(StringBuilder buf, TypeRe
925926
}
926927

927928
#region "Is" methods
928-
private static bool IsOperator(MethodDefinition method)
929-
{
930-
return method.Name.StartsWith("op_", StringComparison.Ordinal);
931-
}
932-
933929
private static bool IsFSharpFunction(TypeReference type)
934930
{
935931
return type.FullName.StartsWith("Microsoft.FSharp.Core.FSharpFunc`");
@@ -984,16 +980,11 @@ protected override StringBuilder AppendVisibility(StringBuilder buf, MethodDefin
984980
{
985981
if (method.IsPublic
986982
|| method.IsFamily
987-
|| method.IsFamilyOrAssembly || IsExplicitlyImplemented(method))
983+
|| method.IsFamilyOrAssembly || DocUtils.IsExplicitlyImplemented(method))
988984
return buf.Append("");
989985
return null;
990986
}
991987

992-
public static bool IsExplicitlyImplemented(MethodDefinition method)
993-
{
994-
return method != null && method.IsPrivate && method.IsFinal && method.IsVirtual;
995-
}
996-
997988
private static string GetTypeVisibility(TypeAttributes ta)
998989
{
999990
switch (ta & TypeAttributes.VisibilityMask)

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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -394,15 +394,14 @@ protected override string GetMethodDeclaration(MethodDefinition method)
394394

395395
protected override StringBuilder AppendMethodName(StringBuilder buf, MethodDefinition method)
396396
{
397-
if (DocUtils.IsExplicitlyImplemented(method))
398-
{
399-
return buf.Append(method.Name.Split('.').Last());
400-
}
397+
var methodName = DocUtils.IsExplicitlyImplemented(method)
398+
? method.Name.Split('.').Last()
399+
: method.Name;
401400

402401
if (DocUtils.IsOperator(method))
403402
{
404403
// this is an operator
405-
switch (method.Name)
404+
switch (methodName)
406405
{
407406
case "op_Implicit":
408407
case "op_Explicit":
@@ -455,11 +454,13 @@ protected override StringBuilder AppendMethodName(StringBuilder buf, MethodDefin
455454
case "op_Like":
456455
return buf.Append("Operator Like");
457456
default:
458-
return base.AppendMethodName(buf, method);
457+
return buf.Append(methodName);
459458
}
460459
}
461460
else
462-
return base.AppendMethodName(buf, method);
461+
{
462+
return buf.Append(methodName);
463+
}
463464
}
464465

465466
protected override StringBuilder AppendGenericMethodConstraints(StringBuilder buf, MethodDefinition method)

mdoc/mdoc.Test/CppFullFormatterTests.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,16 @@ public void MethodSignature_ParamsKeyword_M6()
321321
void M6(int i, ... cli::array <System::Object ^> ^ args);",
322322
"M6");
323323
}
324-
324+
325+
[TestCase("StaticVirtualMembers.Derived",
326+
"StaticVirtualMembers.StaticVirtualMemberInInterface<StaticVirtualMembers.Derived,StaticVirtualMembers.Derived,System.Int32>.op_Addition",
327+
" static int StaticVirtualMembers.StaticVirtualMemberInInterface<StaticVirtualMembers.Derived,StaticVirtualMembers.Derived,System.Int32>.operator +(StaticVirtualMembers::Derived ^ left, StaticVirtualMembers::Derived ^ right) = StaticVirtualMembers::StaticVirtualMemberInInterface<StaticVirtualMembers::Derived ^, StaticVirtualMembers::Derived ^, int>::op_Addition;")]
328+
public void CppCLIStaticOperatorImplementation(string typeFullName, string methodName, string expectedSignature)
329+
{
330+
var staticVirtualMemberDllPath = "../../../../external/Test/StaticVirtualMembers.dll";
331+
TestMethodSignature(staticVirtualMemberDllPath, typeFullName, methodName, expectedSignature);
332+
}
333+
325334
protected override TypeDefinition GetType(Type type)
326335
{
327336
var moduleName = type.Module.FullyQualifiedName;

mdoc/mdoc.Test/FSharp/FSharpFormatterTests.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -904,6 +904,16 @@ public void Operators_3() =>
904904
@"( +? ) : int -> int -> int",
905905
"op_PlusQmark");
906906

907+
[Category("Operators")]
908+
[TestCase("StaticVirtualMembers.Derived",
909+
"StaticVirtualMembers.StaticVirtualMemberInInterface<StaticVirtualMembers.Derived,StaticVirtualMembers.Derived,System.Int32>.op_Addition",
910+
"static member ( + ) : Derived * Derived -> int")]
911+
public void FSharpStaticOperatorImplementation(string typeFullName, string methodName, string expectedSignature)
912+
{
913+
var staticVirtualMemberDllPath = "../../../../external/Test/StaticVirtualMembers.dll";
914+
TestMethodSignature(staticVirtualMemberDllPath, typeFullName, methodName, expectedSignature);
915+
}
916+
907917
#endregion
908918

909919
#region UnitsOfMeasure

mdoc/mdoc.Test/FSharp/FSharpUsageFormatterTests.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,17 @@ public void MethodUsage_11() =>
127127
"func <||| (arg1, arg2, arg3)",
128128
"op_PipeLeft3");
129129

130+
[Category("Usage")]
131+
[Category("Operators")]
132+
[TestCase("StaticVirtualMembers.Derived",
133+
"StaticVirtualMembers.StaticVirtualMemberInInterface<StaticVirtualMembers.Derived,StaticVirtualMembers.Derived,System.Int32>.op_Addition",
134+
"left + right")]
135+
public void FSharpStaticOperatorImplementation(string typeFullName, string methodName, string expectedSignature)
136+
{
137+
var staticVirtualMemberDllPath = "../../../../external/Test/StaticVirtualMembers.dll";
138+
TestMethodSignature(staticVirtualMemberDllPath, typeFullName, methodName, expectedSignature);
139+
}
140+
130141
[Test]
131142
[Category("Usage")]
132143
[Category("Methods")]

0 commit comments

Comments
 (0)