diff --git a/src/linker/Linker/DocumentationSignatureGenerator.PartVisitor.cs b/src/linker/Linker/DocumentationSignatureGenerator.PartVisitor.cs new file mode 100644 index 000000000000..4fa6202efffb --- /dev/null +++ b/src/linker/Linker/DocumentationSignatureGenerator.PartVisitor.cs @@ -0,0 +1,213 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using Mono.Cecil; + +namespace Mono.Linker +{ + + public sealed partial class DocumentationSignatureGenerator + { + /// + /// A visitor that generates the part of the documentation comment after the initial type + /// and colon. + /// Adapted from Roslyn's DocumentattionCommentIDVisitor.PartVisitor: + /// https://github.com/dotnet/roslyn/blob/master/src/Compilers/CSharp/Portable/DocumentationComments/DocumentationCommentIDVisitor.PartVisitor.cs + /// + internal sealed class PartVisitor + { + internal static readonly PartVisitor Instance = new PartVisitor (); + + private PartVisitor () + { + } + + public void VisitArrayType (ArrayType arrayType, StringBuilder builder) + { + VisitTypeReference (arrayType.ElementType, builder); + + // Rank-one arrays are displayed different than rectangular arrays + if (arrayType.IsVector) { + builder.Append ("[]"); + } else { + // C# arrays only support zero lower bounds + if (arrayType.Dimensions[0].LowerBound != 0) + throw new NotImplementedException (); + builder.Append ("[0:"); + for (int i = 1; i < arrayType.Rank; i++) { + if (arrayType.Dimensions[0].LowerBound != 0) + throw new NotImplementedException (); + builder.Append (",0:"); + } + + builder.Append (']'); + } + } + + public void VisitField (FieldDefinition field, StringBuilder builder) + { + VisitTypeReference (field.DeclaringType, builder); + builder.Append ('.').Append (field.Name); + } + + private void VisitParameters (IEnumerable parameters, bool isVararg, StringBuilder builder) + { + builder.Append ('('); + bool needsComma = false; + + foreach (var parameter in parameters) { + if (needsComma) + builder.Append (','); + + // byrefs are tracked on the parameter type, not the parameter, + // so we don't have VisitParameter that Roslyn uses. + VisitTypeReference (parameter.ParameterType, builder); + needsComma = true; + } + + // note: the C# doc comment generator outputs an extra comma for varargs + // methods that also have fixed parameters + if (isVararg && needsComma) + builder.Append (','); + + builder.Append (')'); + } + + public void VisitMethodDefinition (MethodDefinition method, StringBuilder builder) + { + VisitTypeReference (method.DeclaringType, builder); + builder.Append ('.').Append (GetEscapedMetadataName (method)); + + if (method.HasGenericParameters) + builder.Append ("``").Append (method.GenericParameters.Count); + + if (method.HasParameters || (method.CallingConvention == MethodCallingConvention.VarArg)) + VisitParameters (method.Parameters, method.CallingConvention == MethodCallingConvention.VarArg, builder); + + if (method.Name == "op_Implicit" || method.Name == "op_Explicit") { + builder.Append ('~'); + VisitTypeReference (method.ReturnType, builder); + } + } + + public void VisitProperty (PropertyDefinition property, StringBuilder builder) + { + VisitTypeReference (property.DeclaringType, builder); + builder.Append ('.').Append (GetEscapedMetadataName (property)); + + if (property.Parameters.Count > 0) + VisitParameters (property.Parameters, false, builder); + } + + public void VisitEvent (EventDefinition evt, StringBuilder builder) + { + VisitTypeReference (evt.DeclaringType, builder); + builder.Append ('.').Append (GetEscapedMetadataName (evt)); + } + + public void VisitGenericParameter (GenericParameter genericParameter, StringBuilder builder) + { + Debug.Assert ((genericParameter.DeclaringMethod == null) != (genericParameter.DeclaringType == null)); + // Is this a type parameter on a type? + if (genericParameter.DeclaringMethod != null) { + builder.Append ("``"); + } else { + Debug.Assert (genericParameter.DeclaringType != null); + + // If the containing type is nested within other types. + // e.g. A.B.M(T t, U u, V v) should be M(`0, `1, ``0). + // Roslyn needs to add generic arities of parents, but the innermost type redeclares + // all generic parameters so we don't need to add them. + builder.Append ('`'); + } + + builder.Append (genericParameter.Position); + } + + public void VisitTypeReference (TypeReference typeReference, StringBuilder builder) + { + switch (typeReference) { + case ByReferenceType byReferenceType: + VisitByReferenceType (byReferenceType, builder); + return; + case PointerType pointerType: + VisitPointerType (pointerType, builder); + return; + case ArrayType arrayType: + VisitArrayType (arrayType, builder); + return; + case GenericParameter genericParameter: + VisitGenericParameter (genericParameter, builder); + return; + } + + if (typeReference.IsNested) { + VisitTypeReference (typeReference.GetInflatedDeclaringType (), builder); + builder.Append ('.'); + } + + if (!String.IsNullOrEmpty (typeReference.Namespace)) + builder.Append (typeReference.Namespace).Append ('.'); + + // This includes '`n' for mangled generic types + builder.Append (typeReference.Name); + + // For uninstantiated generic types (we already built the mangled name) + // or non-generic types, we are done. + if (typeReference.HasGenericParameters || !typeReference.IsGenericInstance) + return; + + var genericInstance = typeReference as GenericInstanceType; + + // Compute arity counting only the newly-introduced generic parameters + var declaringType = genericInstance.DeclaringType; + var declaringArity = 0; + if (declaringType != null && declaringType.HasGenericParameters) + declaringArity = declaringType.GenericParameters.Count; + var totalArity = genericInstance.GenericArguments.Count; + var arity = totalArity - declaringArity; + + // Un-mangle the generic type name + var suffixLength = arity.ToString ().Length + 1; + builder.Remove (builder.Length - suffixLength, suffixLength); + + // Append type arguments excluding arguments for re-declared parent generic parameters + builder.Append ('{'); + bool needsComma = false; + for (int i = totalArity - arity; i < totalArity; ++i) { + if (needsComma) + builder.Append (','); + var typeArgument = genericInstance.GenericArguments[i]; + VisitTypeReference (typeArgument, builder); + needsComma = true; + } + builder.Append ('}'); + } + + public void VisitPointerType (PointerType pointerType, StringBuilder builder) + { + VisitTypeReference (pointerType.ElementType, builder); + builder.Append ('*'); + } + + public void VisitByReferenceType (ByReferenceType byReferenceType, StringBuilder builder) + { + VisitTypeReference (byReferenceType.ElementType, builder); + builder.Append ('@'); + } + + private static string GetEscapedMetadataName (IMemberDefinition member) + { + var name = member.Name.Replace ('.', '#'); + // Not sure if the following replacements are necessary, but + // they are included to match Roslyn. + return name.Replace ('<', '{').Replace ('>', '}'); + } + } + } +} \ No newline at end of file diff --git a/src/linker/Linker/DocumentationSignatureGenerator.cs b/src/linker/Linker/DocumentationSignatureGenerator.cs new file mode 100644 index 000000000000..3289b189d7cf --- /dev/null +++ b/src/linker/Linker/DocumentationSignatureGenerator.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Text; +using Mono.Cecil; + +namespace Mono.Linker +{ + /// + /// Generates a signature for a member, in the format used for C# Documentation Comments: + /// https://github.com/dotnet/csharplang/blob/master/spec/documentation-comments.md#id-string-format + /// Adapted from Roslyn's DocumentationCommentIDVisitor: + /// https://github.com/dotnet/roslyn/blob/master/src/Compilers/CSharp/Portable/DocumentationComments/DocumentationCommentIDVisitor.cs + /// + public sealed partial class DocumentationSignatureGenerator + { + public static readonly DocumentationSignatureGenerator Instance = new DocumentationSignatureGenerator (); + + private DocumentationSignatureGenerator () + { + } + + public void VisitMethod (MethodDefinition method, StringBuilder builder) + { + builder.Append ("M:"); + PartVisitor.Instance.VisitMethodDefinition (method, builder); + } + + public void VisitField (FieldDefinition field, StringBuilder builder) + { + builder.Append ("F:"); + PartVisitor.Instance.VisitField (field, builder); + } + + public void VisitEvent (EventDefinition evt, StringBuilder builder) + { + builder.Append ("E:"); + PartVisitor.Instance.VisitEvent (evt, builder); + } + + public void VisitProperty (PropertyDefinition property, StringBuilder builder) + { + builder.Append ("P:"); + PartVisitor.Instance.VisitProperty (property, builder); + } + + public void VisitTypeDefinition (TypeDefinition type, StringBuilder builder) + { + builder.Append ("T:"); + PartVisitor.Instance.VisitTypeReference (type, builder); + } + } +} \ No newline at end of file diff --git a/src/linker/Linker/DocumentationSignatureParser.cs b/src/linker/Linker/DocumentationSignatureParser.cs new file mode 100644 index 000000000000..a9a0c191059f --- /dev/null +++ b/src/linker/Linker/DocumentationSignatureParser.cs @@ -0,0 +1,699 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using Mono.Cecil; +using Mono.Collections.Generic; + +namespace Mono.Linker +{ + /// + /// Parses a signature for a member, in the format used for C# Documentation Comments: + /// https://github.com/dotnet/csharplang/blob/master/spec/documentation-comments.md#id-string-format + /// Adapted from Roslyn's DocumentationCommentId: + /// https://github.com/dotnet/roslyn/blob/master/src/Compilers/Core/Portable/DocumentationCommentId.cs + /// + /// + /// Roslyn's API works with ISymbol, which represents a symbol exposed by the compiler. + /// a Symbol has information about the source language, name, metadata name, + /// containing scopes, visibility/accessibility, attributes, etc. + /// This API instead works with the Cecil OM. It can be used to refer to IL definitions + /// where the signature of a member can contain references to instantiated generics. + /// + public static class DocumentationSignatureParser + { + [Flags] + public enum MemberType + { + Method = 0x0001, + Field = 0x0002, + Type = 0x0004, + Property = 0x0008, + Event = 0x0010, + All = Method | Field | Type | Property | Event + } + + public static IEnumerable GetMembersForDocumentationSignature (string id, ModuleDefinition module) + { + if (id == null) + throw new ArgumentNullException (nameof (id)); + + if (module == null) + throw new ArgumentNullException (nameof (module)); + + var results = new List (); + ParseDocumentationSignature (id, module, results); + return results; + } + + static string GetSignaturePart (TypeReference type) + { + var builder = new StringBuilder (); + DocumentationSignatureGenerator.PartVisitor.Instance.VisitTypeReference (type, builder); + return builder.ToString (); + } + + static bool ParseDocumentationSignature (string id, ModuleDefinition module, List results) + { + if (id == null) + return false; + + if (id.Length < 2) + return false; + + int index = 0; + results.Clear (); + ParseSignature (id, ref index, module, results); + return results.Count > 0; + } + + static void ParseSignature (string id, ref int index, ModuleDefinition module, List results) + { + Debug.Assert (results.Count == 0); + var memberTypeChar = PeekNextChar (id, index); + MemberType memberType; + + switch (memberTypeChar) { + case 'E': + memberType = MemberType.Event; + break; + case 'F': + memberType = MemberType.Field; + break; + case 'M': + memberType = MemberType.Method; + break; + case 'N': + // We do not support namespaces, which do not exist in IL. + return; + case 'P': + memberType = MemberType.Property; + break; + case 'T': + memberType = MemberType.Type; + break; + default: + // Documentation comment id must start with E, F, M, P, or T + return; + } + + index++; + // Note: this allows leaving out the ':'. + if (PeekNextChar (id, index) == ':') + index++; + + ParseSignaturePart (id, ref index, module, memberType, results); + } + + // Parses and resolves a fully-qualified (namespace and nested types but no assembly) member signature, + // without the member type prefix. The results include all members matching the specified member types. + public static void ParseSignaturePart (string id, ref int index, ModuleDefinition module, MemberType memberTypes, List results) + { + // Roslyn resolves types by searching namespaces top-down. + // We don't have namespace info. Instead try treating each part of a + // dotted name as a type first, then as a namespace if it fails + // to resolve to a type. + TypeDefinition? containingType = null; + var nameBuilder = new StringBuilder (); + + string name; + int arity; + + // process dotted names + while (true) { + (name, arity) = ParseTypeOrNamespaceName (id, ref index, nameBuilder); + // if we are at the end of the dotted name and still haven't resolved it to + // a type, there are no results. + if (String.IsNullOrEmpty (name)) + return; + + // no more dots, so don't loop any more + if (PeekNextChar (id, index) != '.') + break; + + // must be a namespace or type since name continues after dot + index++; + + // try to resolve it as a type + var typeOrNamespaceName = nameBuilder.ToString (); + GetMatchingTypes (module, declaringType: containingType, name: typeOrNamespaceName, results: results); + Debug.Assert (results.Count <= 1); + if (results.Any ()) { + // the name resolved to a type + var result = results.Single (); + Debug.Assert (result is TypeDefinition); + // result becomes the new container + containingType = result as TypeDefinition; + nameBuilder.Clear (); + results.Clear (); + continue; + } + + // it didn't resolve as a type. + + // only types have arity. + if (arity > 0) + return; + + // treat it as a namespace and continue building up the type name + nameBuilder.Append ('.'); + } + + var memberName = nameBuilder.ToString (); + GetMatchingMembers (id, ref index, module, containingType, memberName, arity, memberTypes, results); + } + + // Gets all members of the specified member kinds of the containing type, with + // mathing name, arity, and signature at the current index (for methods and properties). + // This will also resolve types from the given module if no containing type is given. + public static void GetMatchingMembers (string id, ref int index, ModuleDefinition module, TypeDefinition? containingType, string memberName, int arity, MemberType memberTypes, List results) + { + if (memberTypes.HasFlag (MemberType.Type)) + GetMatchingTypes (module, containingType, memberName, results); + + if (containingType == null) + return; + + int startIndex = index; + int endIndex = index; + + if (memberTypes.HasFlag (MemberType.Method)) { + GetMatchingMethods (id, ref index, containingType, memberName, arity, results); + endIndex = index; + index = startIndex; + } + + if (memberTypes.HasFlag (MemberType.Property)) { + GetMatchingProperties (id, ref index, containingType, memberName, results); + endIndex = index; + index = startIndex; + } + + index = endIndex; + + if (memberTypes.HasFlag (MemberType.Event)) + GetMatchingEvents (containingType, memberName, results); + + if (memberTypes.HasFlag (MemberType.Field)) + GetMatchingFields (containingType, memberName, results); + } + + // Parses a part of a dotted declaration name, including generic definitions. + // Returns the name (either a namespace or the unmangled name of a C# type) and an arity + // which may be non-zero for generic types. + public static (string name, int arity) ParseTypeOrNamespaceName (string id, ref int index, StringBuilder nameBuilder) + { + var name = ParseName (id, ref index); + // don't parse ` after an empty name + if (string.IsNullOrEmpty (name)) + return (name, 0); + + nameBuilder.Append (name); + var arity = 0; + + // has type parameters? + if (PeekNextChar (id, index) == '`') { + index++; + + bool genericType = true; + + // method type parameters? + // note: this allows `` for type parameters + if (PeekNextChar (id, index) == '`') { + index++; + genericType = false; + } + + arity = ReadNextInteger (id, ref index); + + if (genericType) { + // We need to mangle generic type names but not generic method names. + nameBuilder.Append ('`'); + nameBuilder.Append (arity); + } + } + + return (name, arity); + } + + // Roslyn resolves types in a signature to their declaration by searching through namespaces. + // To avoid looking for types by name in all referenced assemblies, we just represent types + // that are part of a signature by their doc comment strings, and we check for matching + // strings when looking for matching member signatures. + static string? ParseTypeSymbol (string id, ref int index, IGenericParameterProvider? typeParameterContext) + { + var results = new List (); + ParseTypeSymbol (id, ref index, typeParameterContext, results); + if (results.Count == 1) + return results[0]; + + Debug.Assert (results.Count == 0); + return null; + } + + static void ParseTypeSymbol (string id, ref int index, IGenericParameterProvider? typeParameterContext, List results) + { + // Note: Roslyn has a special case that deviates from the language spec, which + // allows context expressions embedded in a type reference => : + // We do not support this special format. + + Debug.Assert (results.Count == 0); + + if (PeekNextChar (id, index) == '`') + ParseTypeParameterSymbol (id, ref index, typeParameterContext, results); + else + ParseNamedTypeSymbol (id, ref index, typeParameterContext, results); + + // apply any array or pointer constructions to results + var startIndex = index; + var endIndex = index; + + for (int i = 0; i < results.Count; i++) { + index = startIndex; + var typeReference = results[i]; + + while (true) { + if (PeekNextChar (id, index) == '[') { + var boundsStartIndex = index; + var bounds = ParseArrayBounds (id, ref index); + var boundsEndIndex = index; + Debug.Assert (bounds > 0); + // Instead of constructing a representation of the array bounds, we + // use the original input to represent the bounds, and later match it + // against the generated strings for types in signatures. + // This ensures that we will only resolve members with supported array bounds. + typeReference += id.Substring (boundsStartIndex, boundsEndIndex - boundsStartIndex); + continue; + } + + if (PeekNextChar (id, index) == '*') { + index++; + typeReference += '*'; + continue; + } + + break; + } + + if (PeekNextChar (id, index) == '@') { + index++; + typeReference += '@'; + } + + results[i] = typeReference; + endIndex = index; + } + + index = endIndex; + } + + static void ParseTypeParameterSymbol (string id, ref int index, IGenericParameterProvider? typeParameterContext, List results) + { + // skip the first ` + Debug.Assert (PeekNextChar (id, index) == '`'); + index++; + + Debug.Assert ( + typeParameterContext == null || + (typeParameterContext is MethodDefinition && typeParameterContext.GenericParameterType == GenericParameterType.Method) || + (typeParameterContext is TypeDefinition && typeParameterContext.GenericParameterType == GenericParameterType.Type) + ); + + if (PeekNextChar (id, index) == '`') { + // `` means this is a method type parameter + index++; + var methodTypeParameterIndex = ReadNextInteger (id, ref index); + + if (typeParameterContext is MethodDefinition methodContext) { + var count = methodContext.HasGenericParameters ? methodContext.GenericParameters.Count : 0; + if (count > 0 && methodTypeParameterIndex < count) { + results.Add ("``" + methodTypeParameterIndex); + } + } + } else { + // regular type parameter + var typeParameterIndex = ReadNextInteger (id, ref index); + + var typeContext = typeParameterContext is MethodDefinition methodContext + ? methodContext.DeclaringType + : typeParameterContext as TypeDefinition; + + if (typeParameterIndex >= 0 || + typeParameterIndex < typeContext?.GenericParameters.Count) { + // No need to look at declaring types like Roslyn, because type parameters are redeclared. + results.Add ("`" + typeParameterIndex); + } + } + } + + static void ParseNamedTypeSymbol (string id, ref int index, IGenericParameterProvider? typeParameterContext, List results) + { + Debug.Assert (results.Count == 0); + var nameBuilder = new StringBuilder (); + // loop for dotted names + while (true) { + var name = ParseName (id, ref index); + if (String.IsNullOrEmpty (name)) + return; + + nameBuilder.Append (name); + + List? typeArguments = null; + int arity = 0; + + // type arguments + if (PeekNextChar (id, index) == '{') { + typeArguments = new List (); + if (!ParseTypeArguments (id, ref index, typeParameterContext, typeArguments)) { + continue; + } + + arity = typeArguments.Count; + } + + if (arity != 0) { + Debug.Assert (typeArguments != null && typeArguments.Count != 0); + nameBuilder.Append ('{'); + bool needsComma = false; + foreach (var typeArg in typeArguments) { + if (needsComma) { + nameBuilder.Append (','); + } + nameBuilder.Append (typeArg); + needsComma = true; + } + nameBuilder.Append ('}'); + } + + if (PeekNextChar (id, index) != '.') + break; + + index++; + nameBuilder.Append ('.'); + } + + results.Add (nameBuilder.ToString ()); + } + + static int ParseArrayBounds (string id, ref int index) + { + index++; // skip '[' + + int bounds = 0; + + while (true) { + // note: the actual bounds are ignored. + // C# only supports arrays with lower bound zero. + // size is not known. + + if (char.IsDigit (PeekNextChar (id, index))) + ReadNextInteger (id, ref index); + + if (PeekNextChar (id, index) == ':') { + index++; + + // note: the spec says that omitting both the lower bounds and the size + // should omit the ':' as well, but this allows for it in the input. + if (char.IsDigit (PeekNextChar (id, index))) + ReadNextInteger (id, ref index); + } + + bounds++; + + if (PeekNextChar (id, index) == ',') { + index++; + continue; + } + + break; + } + + // note: this allows leaving out the closing ']' + if (PeekNextChar (id, index) == ']') + index++; + + return bounds; + } + + static bool ParseTypeArguments (string id, ref int index, IGenericParameterProvider? typeParameterContext, List typeArguments) + { + index++; // skip over { + + while (true) { + var type = ParseTypeSymbol (id, ref index, typeParameterContext); + + if (type == null) { + // if a type argument cannot be identified, argument list is no good + return false; + } + + // add first one + typeArguments.Add (type); + + if (PeekNextChar (id, index) == ',') { + index++; + continue; + } + + break; + } + + // note: this doesn't require closing } + if (PeekNextChar (id, index) == '}') { + index++; + } + + return true; + } + + static void GetMatchingTypes (ModuleDefinition module, TypeDefinition? declaringType, string name, List results) + { + Debug.Assert (module != null); + + if (declaringType == null) { + var type = module.GetType (name); + if (type != null) { + results.Add (type); + } + return; + } + + if (!declaringType.HasNestedTypes) + return; + + foreach (var nestedType in declaringType.NestedTypes) { + Debug.Assert (String.IsNullOrEmpty (nestedType.Namespace)); + if (nestedType.Name != name) + continue; + results.Add (nestedType); + return; + } + } + + static void GetMatchingMethods (string id, ref int index, TypeDefinition? type, string memberName, int arity, List results) + { + if (type == null) + return; + + var parameters = new List (); + var startIndex = index; + var endIndex = index; + + foreach (var method in type.Methods) { + index = startIndex; + if (method.Name != memberName) + continue; + + var methodArity = method.HasGenericParameters ? method.GenericParameters.Count : 0; + if (methodArity != arity) + continue; + + parameters.Clear (); + if (PeekNextChar (id, index) == '(') { + // if the parameters cannot be identified (some error), then the symbol cannot match, try next method symbol + if (!ParseParameterList (id, ref index, method, parameters)) + continue; + } + + // note: this allows extra characters at the end + + if (!AllParametersMatch (method.Parameters, parameters)) + continue; + + if (PeekNextChar (id, index) == '~') { + index++; + string? returnType = ParseTypeSymbol (id, ref index, method); + if (returnType == null) + continue; + + // if return type is specified, then it must match + if (GetSignaturePart (method.ReturnType) == returnType) { + results.Add (method); + endIndex = index; + } + } else { + // no return type specified, then any matches + results.Add (method); + endIndex = index; + } + } + index = endIndex; + } + + static void GetMatchingProperties (string id, ref int index, TypeDefinition? type, string memberName, List results) + { + if (type == null) + return; + + int startIndex = index; + int endIndex = index; + + List? parameters = null; + // Unlike Roslyn, we don't need to decode property names because we are working + // directly with IL. + foreach (var property in type.Properties) { + index = startIndex; + if (property.Name != memberName) + continue; + if (PeekNextChar (id, index) == '(') { + if (parameters == null) { + parameters = new List (); + } else { + parameters.Clear (); + } + + if (ParseParameterList (id, ref index, property.DeclaringType, parameters) + && AllParametersMatch (property.Parameters, parameters)) { + results.Add (property); + endIndex = index; + } + } else if (property.Parameters.Count == 0) { + results.Add (property); + endIndex = index; + } + } + + index = endIndex; + } + + static void GetMatchingFields (TypeDefinition? type, string memberName, List results) + { + if (type == null) + return; + foreach (var field in type.Fields) { + if (field.Name != memberName) + continue; + results.Add (field); + } + } + + static void GetMatchingEvents (TypeDefinition? type, string memberName, List results) + { + if (type == null) + return; + foreach (var evt in type.Events) { + if (evt.Name != memberName) + continue; + results.Add (evt); + } + } + + static bool AllParametersMatch (Collection methodParameters, List expectedParameters) + { + if (methodParameters.Count != expectedParameters.Count) + return false; + + for (int i = 0; i < expectedParameters.Count; i++) { + if (GetSignaturePart (methodParameters[i].ParameterType) != expectedParameters[i]) + return false; + } + + return true; + } + + static bool ParseParameterList (string id, ref int index, IGenericParameterProvider typeParameterContext, List parameters) + { + System.Diagnostics.Debug.Assert (typeParameterContext != null); + + index++; // skip over '(' + + if (PeekNextChar (id, index) == ')') { + // note: this will match parameterless methods, or methods with only varargs parameters + index++; + return true; + } + + string? parameter = ParseTypeSymbol (id, ref index, typeParameterContext); + if (parameter == null) + return false; + + parameters.Add (parameter); + + while (PeekNextChar (id, index) == ',') { + index++; + + parameter = ParseTypeSymbol (id, ref index, typeParameterContext); + if (parameter == null) + return false; + + parameters.Add (parameter); + } + + // note: this doesn't require the trailing ')' + if (PeekNextChar (id, index) == ')') { + index++; + } + + return true; + } + + static char PeekNextChar (string id, int index) + { + return index >= id.Length ? '\0' : id[index]; + } + + static readonly char[] s_nameDelimiters = { ':', '.', '(', ')', '{', '}', '[', ']', ',', '\'', '@', '*', '`', '~' }; + + static string ParseName (string id, ref int index) + { + string name; + + int delimiterOffset = id.IndexOfAny (s_nameDelimiters, index); + if (delimiterOffset >= 0) { + name = id.Substring (index, delimiterOffset - index); + index = delimiterOffset; + } else { + name = id.Substring (index); + index = id.Length; + } + + return DecodeName (name); + } + + // undoes dot encodings within names... + static string DecodeName (string name) + { + return name.Replace ('#', '.'); + } + + static int ReadNextInteger (string id, ref int index) + { + int n = 0; + + // note: this can overflow + while (index < id.Length && char.IsDigit (id[index])) { + n = n * 10 + (id[index] - '0'); + index++; + } + + return n; + } + } +} \ No newline at end of file diff --git a/src/linker/Linker/TypeReferenceExtensions.cs b/src/linker/Linker/TypeReferenceExtensions.cs index 6910de04c0f0..8498d788969d 100644 --- a/src/linker/Linker/TypeReferenceExtensions.cs +++ b/src/linker/Linker/TypeReferenceExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Text; using Mono.Cecil; using System.Collections.Generic; using System.Linq; @@ -36,6 +37,42 @@ public static TypeReference GetInflatedBaseType (this TypeReference type) return type.Resolve ()?.BaseType; } + public static TypeReference GetInflatedDeclaringType (this TypeReference type) + { + if (type == null) + return null; + + if (type.IsGenericParameter || type.IsByReference || type.IsPointer) + return null; + + if (type is SentinelType sentinelType) + return sentinelType.ElementType.GetInflatedDeclaringType (); + + if (type is PinnedType pinnedType) + return pinnedType.ElementType.GetInflatedDeclaringType (); + + if (type is RequiredModifierType requiredModifierType) + return requiredModifierType.ElementType.GetInflatedDeclaringType (); + + if (type is GenericInstanceType genericInstance) { + var declaringType = genericInstance.DeclaringType; + + if (declaringType.HasGenericParameters) { + var result = new GenericInstanceType (declaringType); + for (var i = 0; i < declaringType.GenericParameters.Count; ++i) + result.GenericArguments.Add (genericInstance.GenericArguments[i]); + + return result; + } + + return declaringType; + } + + var resolved = type.Resolve (); + System.Diagnostics.Debug.Assert (resolved == type); + return resolved?.DeclaringType; + } + public static IEnumerable GetInflatedInterfaces (this TypeReference typeRef) { var typeDef = typeRef.Resolve (); diff --git a/test/Mono.Linker.Tests.Cases.Expectations/Assertions/BaseMemberAssertionAttribute.cs b/test/Mono.Linker.Tests.Cases.Expectations/Assertions/BaseMemberAssertionAttribute.cs new file mode 100644 index 000000000000..563ebfb8f611 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases.Expectations/Assertions/BaseMemberAssertionAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Mono.Linker.Tests.Cases.Expectations.Assertions +{ + /// A base class for attributes that make assertions about a particular member. + // The test infrastructure is expected to check the assertion on the member to which + // the attribute is applied. + [AttributeUsage (AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Event | AttributeTargets.Delegate, AllowMultiple = true)] + public abstract class BaseMemberAssertionAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases.Expectations/Assertions/ExpectExactlyResolvedDocumentationSignatureAttribute.cs b/test/Mono.Linker.Tests.Cases.Expectations/Assertions/ExpectExactlyResolvedDocumentationSignatureAttribute.cs new file mode 100644 index 000000000000..19d582509dc6 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases.Expectations/Assertions/ExpectExactlyResolvedDocumentationSignatureAttribute.cs @@ -0,0 +1,13 @@ +using System; + +namespace Mono.Linker.Tests.Cases.Expectations.Assertions +{ + /// Asserts that the given documentation signature string resolves to the + // member with this attribute, and only that member. + public class ExpectExactlyResolvedDocumentationSignatureAttribute : BaseMemberAssertionAttribute + { + public ExpectExactlyResolvedDocumentationSignatureAttribute (string input) + { + } + } +} \ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases.Expectations/Assertions/ExpectGeneratedDocumentationSignatureAttribute.cs b/test/Mono.Linker.Tests.Cases.Expectations/Assertions/ExpectGeneratedDocumentationSignatureAttribute.cs new file mode 100644 index 000000000000..2a4402199557 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases.Expectations/Assertions/ExpectGeneratedDocumentationSignatureAttribute.cs @@ -0,0 +1,13 @@ +using System; + +namespace Mono.Linker.Tests.Cases.Expectations.Assertions +{ + /// Asserts that the member to which this attribute is applied has the given + /// documentation signature. + public class ExpectGeneratedDocumentationSignatureAttribute : BaseMemberAssertionAttribute + { + public ExpectGeneratedDocumentationSignatureAttribute (string expected) + { + } + } +} \ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases.Expectations/Assertions/ExpectResolvedDocumentationSignatureAttribute.cs b/test/Mono.Linker.Tests.Cases.Expectations/Assertions/ExpectResolvedDocumentationSignatureAttribute.cs new file mode 100644 index 000000000000..da9bfde4c5bc --- /dev/null +++ b/test/Mono.Linker.Tests.Cases.Expectations/Assertions/ExpectResolvedDocumentationSignatureAttribute.cs @@ -0,0 +1,13 @@ +using System; + +namespace Mono.Linker.Tests.Cases.Expectations.Assertions +{ + /// Asserts that the given documentation signature string resolves to the + // member with this attribute. + public class ExpectResolvedDocumentationSignatureAttribute : BaseMemberAssertionAttribute + { + public ExpectResolvedDocumentationSignatureAttribute (string input) + { + } + } +} \ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases.Expectations/Assertions/ExpectUnresolvedDocumentationSignatureAttribute.cs b/test/Mono.Linker.Tests.Cases.Expectations/Assertions/ExpectUnresolvedDocumentationSignatureAttribute.cs new file mode 100644 index 000000000000..9c71567ac5b3 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases.Expectations/Assertions/ExpectUnresolvedDocumentationSignatureAttribute.cs @@ -0,0 +1,13 @@ +using System; + +namespace Mono.Linker.Tests.Cases.Expectations.Assertions +{ + /// Asserts that the given documentation signature string does not resolve + /// to the member with this attribute. + public class ExpectUnresolvedDocumentationSignatureAttribute : BaseMemberAssertionAttribute + { + public ExpectUnresolvedDocumentationSignatureAttribute (string expected) + { + } + } +} \ No newline at end of file diff --git a/test/Mono.Linker.Tests/Mono.Linker.Tests.csproj b/test/Mono.Linker.Tests/Mono.Linker.Tests.csproj index 8255852b387f..be4a9a3e7283 100644 --- a/test/Mono.Linker.Tests/Mono.Linker.Tests.csproj +++ b/test/Mono.Linker.Tests/Mono.Linker.Tests.csproj @@ -3,6 +3,7 @@ latest + true diff --git a/test/Mono.Linker.Tests/Tests/DocumentationSignatureParserTests.cs b/test/Mono.Linker.Tests/Tests/DocumentationSignatureParserTests.cs new file mode 100644 index 000000000000..2745cd63831b --- /dev/null +++ b/test/Mono.Linker.Tests/Tests/DocumentationSignatureParserTests.cs @@ -0,0 +1,610 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using NUnit.Framework; +using Mono.Cecil; +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Extensions; + +namespace Mono.Linker.Tests +{ + [TestFixture] + public class DocumentationSignatureParserTests + { + [TestCaseSource (nameof (GetMemberAssertionsAsArray), new object[] { typeof (DocumentationSignatureParserTests) })] + public void TestSignatureParsing (IMemberDefinition member, CustomAttribute customAttribute) + { + var attributeString = (string) customAttribute.ConstructorArguments[0].Value; + switch (customAttribute.AttributeType.Name) { + case nameof (ExpectExactlyResolvedDocumentationSignatureAttribute): + CheckUniqueParsedString (member, attributeString); + break; + case nameof (ExpectGeneratedDocumentationSignatureAttribute): + CheckGeneratedString (member, attributeString); + break; + case nameof (ExpectResolvedDocumentationSignatureAttribute): + CheckParsedString (member, attributeString); + break; + case nameof (ExpectUnresolvedDocumentationSignatureAttribute): + CheckUnresolvedDocumentationSignature (member, attributeString); + break; + default: + throw new NotImplementedException (); + } + } + + public static void CheckUniqueParsedString (IMemberDefinition member, string input) + { + var module = (member as TypeDefinition)?.Module ?? member.DeclaringType?.Module; + Assert.NotNull (module); + var parseResults = DocumentationSignatureParser.GetMembersForDocumentationSignature (input, module); + Assert.AreEqual (1, parseResults.Count ()); + Assert.AreEqual (member, parseResults.First ()); + } + + public static void CheckGeneratedString (IMemberDefinition member, string expected) + { + var generator = DocumentationSignatureGenerator.Instance; + var builder = new StringBuilder (); + switch (member) { + case TypeDefinition type: + generator.VisitTypeDefinition (type, builder); + break; + case MethodDefinition method: + generator.VisitMethod (method, builder); + break; + case FieldDefinition field: + generator.VisitField (field, builder); + break; + case PropertyDefinition property: + generator.VisitProperty (property, builder); + break; + case EventDefinition evt: + generator.VisitEvent (evt, builder); + break; + default: + throw new NotImplementedException (); + } + Assert.AreEqual (expected, builder.ToString ()); + } + + public static void CheckParsedString (IMemberDefinition member, string input) + { + var module = (member as TypeDefinition)?.Module ?? member.DeclaringType?.Module; + Assert.NotNull (module); + var parseResults = DocumentationSignatureParser.GetMembersForDocumentationSignature (input, module); + CollectionAssert.Contains (parseResults, member); + } + + public static void CheckUnresolvedDocumentationSignature (IMemberDefinition member, string input) + { + var module = (member as TypeDefinition)?.Module ?? member.DeclaringType?.Module; + Assert.NotNull (module); + var parseResults = DocumentationSignatureParser.GetMembersForDocumentationSignature (input, module); + CollectionAssert.DoesNotContain (parseResults, member); + } + + static IEnumerable<(IMemberDefinition member, CustomAttribute ca)> GetMemberAssertions (Type type) + { + var resolver = new DefaultAssemblyResolver (); + resolver.AddSearchDirectory (Path.GetDirectoryName (type.Assembly.Location)); + var assembly = resolver.Resolve (new AssemblyNameReference (type.Assembly.GetName ().Name, null)); + var t = assembly.MainModule.GetType (type.Namespace + "." + type.Name); + if (t == null) + throw new InvalidOperationException ($"type {type} not found in {assembly}"); + var results = new List<(IMemberDefinition, CustomAttribute)> (); + CollectMemberAssertions (t, results); + return results; + } + + private static bool IsMemberAssertion (TypeReference attributeType) + { + if (attributeType == null) + return false; + + if (attributeType.Namespace != "Mono.Linker.Tests.Cases.Expectations.Assertions") + return false; + + return attributeType.Resolve ().DerivesFrom (nameof (BaseMemberAssertionAttribute)); + } + + private static void CollectMemberAssertions (TypeDefinition type, List<(IMemberDefinition, CustomAttribute)> results) + { + if (type.HasCustomAttributes) { + foreach (var ca in type.CustomAttributes) { + if (!IsMemberAssertion (ca.AttributeType)) + continue; + results.Add ((type, ca)); + } + } + + foreach (var m in type.Methods) { + if (!m.HasCustomAttributes) + continue; + + foreach (var ca in m.CustomAttributes) { + if (!IsMemberAssertion (ca.AttributeType)) + continue; + results.Add ((m, ca)); + } + } + + foreach (var f in type.Fields) { + if (!f.HasCustomAttributes) + continue; + + foreach (var ca in f.CustomAttributes) { + if (!IsMemberAssertion (ca.AttributeType)) + continue; + results.Add ((f, ca)); + } + } + + foreach (var p in type.Properties) { + if (!p.HasCustomAttributes) + continue; + + foreach (var ca in p.CustomAttributes) { + if (!IsMemberAssertion (ca.AttributeType)) + continue; + results.Add ((p, ca)); + } + } + + foreach (var e in type.Events) { + if (!e.HasCustomAttributes) + continue; + + foreach (var ca in e.CustomAttributes) { + if (!IsMemberAssertion (ca.AttributeType)) + continue; + results.Add ((e, ca)); + } + } + + if (!type.HasNestedTypes) + return; + + foreach (var nested in type.NestedTypes) { + CollectMemberAssertions (nested, results); + } + } + + static IEnumerable GetMemberAssertionsAsArray (Type type) + { + return GetMemberAssertions (type).Select (v => new object[] { v.member, v.ca }); + } + + // testcases + + [ExpectGeneratedDocumentationSignature ("T:Mono.Linker.Tests.DocumentationSignatureParserTests.A")] + [ExpectExactlyResolvedDocumentationSignature ("T:Mono.Linker.Tests.DocumentationSignatureParserTests.A")] + public class A + { + [ExpectGeneratedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.#ctor")] + [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.#ctor")] + public A () + { + } + + [ExpectGeneratedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.#ctor(System.Int32)")] + [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.#ctor(System.Int32)")] + public A (int a) + { + } + + [ExpectGeneratedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.#cctor")] + [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.#cctor")] + static A () + { + } + + [ExpectGeneratedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.M(System.Int32[])")] + [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.M(System.Int32[])")] + public void M (int[] a) + { + } + + [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.M(System.Int32,System.Int32,System.Int32)~System.Int32")] + public int M (int a, int b, int c) + { + return 0; + } + + [ExpectGeneratedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.MRef(System.Int32@)")] + [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.MRef(System.Int32@)")] + public void MRef (ref int a) + { + } + + [ExpectGeneratedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.MOut(System.Int32@)")] + [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.MOut(System.Int32@)")] + public void MOut (out int a) + { + a = 5; + } + + [ExpectGeneratedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.MIn(System.Int32@)")] + [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.MIn(System.Int32@)")] + public void MIn (in int a) + { + } + + public static int i; + + [ExpectGeneratedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.MRefReturn")] + [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.MRefReturn")] + [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.MRefReturn~System.Int32@")] + public ref int MRefReturn () + { + return ref i; + } + + [ExpectGeneratedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.M")] + [ExpectResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.M")] // binds to both. + [ExpectResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.M()")] // binds to both. + public void M () + { + } + + [ExpectGeneratedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.M()")] + [ExpectResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.M")] + [ExpectResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.M()")] + public void M (__arglist) + { + } + + [ExpectGeneratedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.M(System.Int32[][])")] + [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.M(System.Int32[][])")] + public void M (int[][] a) + { + } + + [ExpectGeneratedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.M(System.Int32[][0:,0:,0:])")] + [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.M(System.Int32[][0:,0:,0:])")] + public void M (int[,,][] a) + { + } + + [ExpectGeneratedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.M(System.Int32[0:,0:])")] + [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.M(System.Int32[0:,0:])")] + public void M (int[,] a) + { + } + + [ExpectGeneratedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.M(System.Object)")] + [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.M(System.Object)")] + public void M (dynamic d) + { + } + + [ExpectGeneratedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.M(System.Int32*)")] + [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.M(System.Int32*)")] + public unsafe void M (int* a) + { + } + + [ExpectGeneratedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.M``1(Mono.Linker.Tests.DocumentationSignatureParserTests.S{Mono.Linker.Tests.DocumentationSignatureParserTests.G{Mono.Linker.Tests.DocumentationSignatureParserTests.A,``0}}**[0:,0:,0:][][][0:,0:]@)")] + [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.M``1(Mono.Linker.Tests.DocumentationSignatureParserTests.S{Mono.Linker.Tests.DocumentationSignatureParserTests.G{Mono.Linker.Tests.DocumentationSignatureParserTests.A,``0}}**[0:,0:,0:][][][0:,0:]@)")] + public unsafe void M (ref S>**[,][][][,,] a) + { + } + + [ExpectGeneratedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.M(System.Collections.Generic.List{System.Int32[]})")] + [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.M(System.Collections.Generic.List{System.Int32[]})")] + public void M (List a) + { + } + + [ExpectGeneratedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.M(System.Int32,)")] + //[ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.M(System.Int32,)")] + // there's no way to reference this, since the parsing logic doesn't like it. + public void M (int abo, __arglist) + { + } + + [ExpectGeneratedDocumentationSignature ("P:Mono.Linker.Tests.DocumentationSignatureParserTests.A.Prop")] + [ExpectExactlyResolvedDocumentationSignature ("P:Mono.Linker.Tests.DocumentationSignatureParserTests.A.Prop")] + public int Prop { get; set; } + + [ExpectGeneratedDocumentationSignature ("F:Mono.Linker.Tests.DocumentationSignatureParserTests.A.field")] + [ExpectExactlyResolvedDocumentationSignature ("F:Mono.Linker.Tests.DocumentationSignatureParserTests.A.field")] + public int field; + + + [ExpectGeneratedDocumentationSignature ("E:Mono.Linker.Tests.DocumentationSignatureParserTests.A.OnEvent")] + [ExpectExactlyResolvedDocumentationSignature ("E:Mono.Linker.Tests.DocumentationSignatureParserTests.A.OnEvent")] + public event EventHandler OnEvent; + + [ExpectGeneratedDocumentationSignature ("E:Mono.Linker.Tests.DocumentationSignatureParserTests.A.OnEventInt")] + [ExpectExactlyResolvedDocumentationSignature ("E:Mono.Linker.Tests.DocumentationSignatureParserTests.A.OnEventInt")] + public event Action OnEventInt; + + [ExpectGeneratedDocumentationSignature ("T:Mono.Linker.Tests.DocumentationSignatureParserTests.A.Del")] + [ExpectExactlyResolvedDocumentationSignature ("T:Mono.Linker.Tests.DocumentationSignatureParserTests.A.Del")] + public delegate int Del (int a, int b); + + [ExpectGeneratedDocumentationSignature ("E:Mono.Linker.Tests.DocumentationSignatureParserTests.A.OnEventDel")] + [ExpectExactlyResolvedDocumentationSignature ("E:Mono.Linker.Tests.DocumentationSignatureParserTests.A.OnEventDel")] + public event Del OnEventDel; + + // prevent warning about unused events + public void UseEvents () + { + OnEventDel?.Invoke (1, 2); + OnEventInt?.Invoke (1); + OnEvent?.Invoke (null, null); + } + + [ExpectGeneratedDocumentationSignature ("P:Mono.Linker.Tests.DocumentationSignatureParserTests.A.Item(System.Int32)")] + [ExpectExactlyResolvedDocumentationSignature ("P:Mono.Linker.Tests.DocumentationSignatureParserTests.A.Item(System.Int32)")] + public int this[int i] { + get => 0; + set { } + } + + [ExpectGeneratedDocumentationSignature ("P:Mono.Linker.Tests.DocumentationSignatureParserTests.A.Item(System.String,System.Int32)")] + [ExpectExactlyResolvedDocumentationSignature ("P:Mono.Linker.Tests.DocumentationSignatureParserTests.A.Item(System.String,System.Int32)")] + public int this[string s, int i] { + get => 0; + set { } + } + + [ExpectGeneratedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.op_Implicit(Mono.Linker.Tests.DocumentationSignatureParserTests.A)~System.Boolean")] + [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.op_Implicit(Mono.Linker.Tests.DocumentationSignatureParserTests.A)~System.Boolean")] + public static implicit operator bool (A a) => false; + + // C# will not generate a return type for this method, but we will. + // [ExpectGeneratedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.op_Implicit(Mono.Linker.Tests.DocumentationSignatureParserTests.A)~System.Boolean")] + // [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.op_Implicit(Mono.Linker.Tests.DocumentationSignatureParserTests.A)~System.Boolean")] + //public static int op_Implicit (A a) => 0; + + [ExpectGeneratedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.op_UnaryPlus(Mono.Linker.Tests.DocumentationSignatureParserTests.A)")] + [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.op_UnaryPlus(Mono.Linker.Tests.DocumentationSignatureParserTests.A)")] + public static A operator + (A a) => null; + + [ExpectGeneratedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.op_Addition(Mono.Linker.Tests.DocumentationSignatureParserTests.A,Mono.Linker.Tests.DocumentationSignatureParserTests.A)")] + [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.A.op_Addition(Mono.Linker.Tests.DocumentationSignatureParserTests.A,Mono.Linker.Tests.DocumentationSignatureParserTests.A)")] + public static A operator + (A left, A right) => null; + } + + public struct S + { + } + + [ExpectGeneratedDocumentationSignature ("T:Mono.Linker.Tests.DocumentationSignatureParserTests.A`1")] + [ExpectExactlyResolvedDocumentationSignature ("T:Mono.Linker.Tests.DocumentationSignatureParserTests.A`1")] + public class A + { + [ExpectGeneratedDocumentationSignature ("P:Mono.Linker.Tests.DocumentationSignatureParserTests.A`1.Item(`0)")] + [ExpectExactlyResolvedDocumentationSignature ("P:Mono.Linker.Tests.DocumentationSignatureParserTests.A`1.Item(`0)")] + public int this[T t] { + get => 0; + set { } + } + } + + [ExpectExactlyResolvedDocumentationSignature ("T:Mono.Linker.Tests.DocumentationSignatureParserTests.B")] + [ExpectGeneratedDocumentationSignature ("T:Mono.Linker.Tests.DocumentationSignatureParserTests.B")] + public class B + { + [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.B.Method(Mono.Linker.Tests.DocumentationSignatureParserTests.G{Mono.Linker.Tests.DocumentationSignatureParserTests.A{Mono.Linker.Tests.DocumentationSignatureParserTests.B},System.Collections.Generic.List{Mono.Linker.Tests.DocumentationSignatureParserTests.A}})")] + [ExpectGeneratedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.B.Method(Mono.Linker.Tests.DocumentationSignatureParserTests.G{Mono.Linker.Tests.DocumentationSignatureParserTests.A{Mono.Linker.Tests.DocumentationSignatureParserTests.B},System.Collections.Generic.List{Mono.Linker.Tests.DocumentationSignatureParserTests.A}})")] + public void Method (G, List> l) + { + } + } + + [ExpectGeneratedDocumentationSignature ("T:Mono.Linker.Tests.DocumentationSignatureParserTests.G`2")] + [ExpectExactlyResolvedDocumentationSignature ("T:Mono.Linker.Tests.DocumentationSignatureParserTests.G`2")] + public class G + { + [ExpectGeneratedDocumentationSignature ("T:Mono.Linker.Tests.DocumentationSignatureParserTests.G`2.NG`1")] + [ExpectExactlyResolvedDocumentationSignature ("T:Mono.Linker.Tests.DocumentationSignatureParserTests.G`2.NG`1")] + public class NG + { + [ExpectGeneratedDocumentationSignature ("T:Mono.Linker.Tests.DocumentationSignatureParserTests.G`2.NG`1.NG2`1")] + [ExpectExactlyResolvedDocumentationSignature ("T:Mono.Linker.Tests.DocumentationSignatureParserTests.G`2.NG`1.NG2`1")] + public class NG2 + { + [ExpectGeneratedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.G`2.NG`1.NG2`1.Method``1(`0,`1,`2,`3,``0)")] + [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.G`2.NG`1.NG2`1.Method``1(`0,`1,`2,`3,``0)")] + public void Method (T t, U u, V v, W w, X x) + { + } + + [ExpectGeneratedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.G`2.NG`1.NG2`1.Method(Mono.Linker.Tests.DocumentationSignatureParserTests.G{`0,`1}.NG{`2}.NG2{`3})")] + [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.G`2.NG`1.NG2`1.Method(Mono.Linker.Tests.DocumentationSignatureParserTests.G{`0,`1}.NG{`2}.NG2{`3})")] + public void Method (NG2 n) + { + } + } + } + + [ExpectGeneratedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.G`2.Method(Mono.Linker.Tests.DocumentationSignatureParserTests.G{`0,`1})")] + [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.G`2.Method(Mono.Linker.Tests.DocumentationSignatureParserTests.G{`0,`1})")] + public void Method (G g) + { + } + } + + [ExpectGeneratedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.Method")] + [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.Method")] + public void Method () + { + } + + [ExpectGeneratedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.Method(System.Int32)")] + [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.Method(System.Int32)")] + public void Method (int i) + { + } + + [ExpectGeneratedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.IntMethod")] + [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.IntMethod")] + public int IntMethod () => 0; + + [ExpectGeneratedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.Method(Mono.Linker.Tests.DocumentationSignatureParserTests.G{Mono.Linker.Tests.DocumentationSignatureParserTests.A,Mono.Linker.Tests.DocumentationSignatureParserTests.A})")] + [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.Method(Mono.Linker.Tests.DocumentationSignatureParserTests.G{Mono.Linker.Tests.DocumentationSignatureParserTests.A,Mono.Linker.Tests.DocumentationSignatureParserTests.A})")] + public void Method (G g) + { + } + + [ExpectGeneratedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.Method(Mono.Linker.Tests.DocumentationSignatureParserTests.G{Mono.Linker.Tests.DocumentationSignatureParserTests.A,Mono.Linker.Tests.DocumentationSignatureParserTests.A}.NG{Mono.Linker.Tests.DocumentationSignatureParserTests.A})")] + [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.Method(Mono.Linker.Tests.DocumentationSignatureParserTests.G{Mono.Linker.Tests.DocumentationSignatureParserTests.A,Mono.Linker.Tests.DocumentationSignatureParserTests.A}.NG{Mono.Linker.Tests.DocumentationSignatureParserTests.A})")] + public void Method (G.NG g) + { + } + + public class Invalid + { + [ExpectUnresolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.NoReturnType~")] + public int NoReturnType () => 0; + + [ExpectUnresolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.NoParameters(,)")] + public void NoParameters (int a, int b) + { + } + + [ExpectUnresolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.NoClosingParen(")] + public void NoClosingParen () { } + + [ExpectUnresolvedDocumentationSignature ("T:Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.Whitespace ")] + [ExpectUnresolvedDocumentationSignature (" T:Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.Whitespace")] + [ExpectUnresolvedDocumentationSignature ("T: Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.Whitespace")] + [ExpectUnresolvedDocumentationSignature ("T :Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.Whitespace")] + [ExpectUnresolvedDocumentationSignature ("")] + [ExpectUnresolvedDocumentationSignature (" ")] + public class Whitespace + { + [ExpectUnresolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.Whitespace.Method(System.Int32, System.Int32)")] + public void Method (int a, int b) + { + } + + [ExpectUnresolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.Whitespace.Method(Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.Generic{System.Int32, System.Int32})")] + public void Method (Generic g) + { + } + } + + public class Generic + { + } + + [ExpectUnresolvedDocumentationSignature ("T:Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.Generic{`1}")] + [ExpectUnresolvedDocumentationSignature ("T:Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.Generic{T}")] + [ExpectUnresolvedDocumentationSignature ("T:Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.Generic")] + public class Generic + { + [ExpectUnresolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.Generic``1.MethodSyntaxForTypeParameter(`0)")] + + public void MethodSyntaxForTypeParameter (T t) + { + } + + [ExpectUnresolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.Generic`1.MethodSyntaxForTypeGenericArgument(Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.Generic{``0})")] + public void MethodSyntaxForTypeGenericArgument (Generic g) + { + } + + [ExpectUnresolvedDocumentationSignature ("P:Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.Generic`1.Item(``0)")] + public bool this[T t] { + get => false; + set { } + } + } + + [ExpectUnresolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.MethodWithGenericInstantiation(Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.Generic`1)")] + public void MethodWithGenericInstantiation (Generic g) + { + } + + [ExpectUnresolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.Method(System.Int32[:,:])")] + [ExpectUnresolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.Method(System.Int32[0:,)")] + public void Method (int[,] a) + { + } + + [ExpectUnresolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.NonGenericMethod(``0)")] + public void NonGenericMethod (int i) + { + } + + [ExpectUnresolvedDocumentationSignature ("P:Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.Item(`0)")] + [ExpectUnresolvedDocumentationSignature ("P:Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.Item(``0)")] + public int this[int i] { + get => 0; + set { } + } + + [ExpectUnresolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.MethodMissingArgumentTypeName(System.)")] + public void MethodMissingArgumentTypeName (int i) + { + } + + [ExpectUnresolvedDocumentationSignature ("T:Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.")] + public class NoType + { + [ExpectUnresolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid..Method")] + public void Method () + { + } + } + + // prevent warning about unused events + public void UseEvents () + { + OnEvent?.Invoke (null, null); + OnEventArgs?.Invoke (null, null); + } + + [ExpectUnresolvedDocumentationSignature ("T:Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.NoParameterType()")] + public void NoParameterType (int i) + { + } + + [ExpectUnresolvedDocumentationSignature ("T:Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.NoParameterType(Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.Generic{})")] + public void NoGenericParameterType (Generic g) + { + } + + // our parser won't match fields with `, unlike roslyn. + [ExpectUnresolvedDocumentationSignature ("F:Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.field`gibberish")] + public int field; + + [ExpectUnresolvedDocumentationSignature ("E:Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.OnEvent`gibberish")] + public event EventHandler OnEvent; + + // the below work, but seem like they shouldn't. See https://github.com/mono/linker/issues/1214. + + [ExpectExactlyResolvedDocumentationSignature ("TMono.Linker.Tests.DocumentationSignatureParserTests.Invalid.NoColon")] + public class NoColon + { + } + + [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.NoClosingParenWithParameters(System.Int32")] + public void NoClosingParenWithParameters (int a) + { + } + + [ExpectExactlyResolvedDocumentationSignature ("M:Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.NoClosingBrace(Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.Generic{Mono.Linker.Tests.DocumentationSignatureParserTests.A)")] + public void NoClosingBrace (Generic g) + { + } + + [ExpectExactlyResolvedDocumentationSignature ("F:Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.fieldArgs(gibberish")] + public int fieldArgs; + + [ExpectExactlyResolvedDocumentationSignature ("E:Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.OnEventArgs(gibberish")] + public event EventHandler OnEventArgs; + + [ExpectExactlyResolvedDocumentationSignature ("T:Mono.Linker.Tests.DocumentationSignatureParserTests.Invalid.NestedType{")] + public class NestedType + { + } + } + } +} \ No newline at end of file