<commit_before>﻿// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection.Metadata;
using System.Text;

namespace Microsoft.Fx.Portability.Analyzer
{
    internal class MemberMetadataInfo
    {
        public MemberMetadataInfo()
        {
            Kind = MemberKind.Type;
            Names = new List<string>();
        }

        public MemberMetadataInfo(MemberMetadataInfo other)
        {
            Names = other.Names;
            MethodSignature = other.MethodSignature;
            Module = other.Module;
            IsArrayType = other.IsArrayType;
            ArrayTypeInfo = other.ArrayTypeInfo;
            IsTypeDef = other.IsTypeDef;
            IsPrimitiveType = other.IsPrimitiveType;
            IsFunctionPointer = other.IsFunctionPointer;
            Kind = other.Kind;
            IsGenericInstance = other.IsGenericInstance;
            IsEnclosedType = other.IsEnclosedType;
            Name = other.Name;
            Namespace = other.Namespace;
            DefinedInAssembly = other.DefinedInAssembly;
            IsAssemblySet = other.IsAssemblySet;

            if (other.ParentType != null)
            {
                ParentType = new MemberMetadataInfo(other.ParentType);
            }

            if (other.GenericTypeArgs != null)
            {
                GenericTypeArgs = other.GenericTypeArgs.Select(o => new MemberMetadataInfo(o)).ToList();
            }
        }

        public MemberMetadataInfo(MemberMetadataInfo other1, MemberMetadataInfo other2)
            : this(other1)
        {
            Names.AddRange(other2.Names);
            Names.Add(other2.Name);

            if (other2.Namespace != null)
            {
                Namespace = other2.Namespace;
            }

            if (other2.IsAssemblySet)
            {
                DefinedInAssembly = other2.DefinedInAssembly;
                IsAssemblySet = true;
            }
        }

        public List<string> Names { get; set; }

        public MethodSignature<MemberMetadataInfo> MethodSignature { get; set; }

        public ModuleReferenceHandle Module { get; set; }

        public List<MemberMetadataInfo> GenericTypeArgs { get; set; }

        public bool IsArrayType { get; set; }

        public string ArrayTypeInfo { get; set; }

        public bool IsTypeDef { get; set; }

        public bool IsPrimitiveType { get; set; }

        public bool IsFunctionPointer { get; set; }

        public MemberKind Kind { get; set; }

        public bool IsGenericInstance { get; set; }

        public bool IsEnclosedType { get; set; }

        public string Name { get; set; }

        public string Namespace { get; set; }

        public MemberMetadataInfo ParentType { get; set; }

        public AssemblyReference DefinedInAssembly { get; set; }

        public bool IsAssemblySet { get; set; }

        public override string ToString()
        {
            if (Kind == MemberKind.Method || Kind == MemberKind.Field)
            {
                return GenerateMemberDocId();
            }
            else
            {
                return GenerateTypeDocId();
            }
        }

        private string GenerateTypeDocId()
        {
            var sb = new StringBuilder();

            if (Namespace != null)
            {
                sb.Append(Namespace);
                sb.Append(".");
            }

            var combinedNames = new List<string>(Names) { Name };
            var displayNames = IsGenericInstance && GenericTypeArgs != null && IsEnclosedType
                ? GetGenericDisplayNames(combinedNames) : combinedNames;

            sb.Append(string.Join(".", displayNames));

            return sb.ToString();
        }

        /// <summary>
        /// Add the type arguments for generic instances. Go through all type names, and if it is generic, such 
        /// as Hashtable`1, remove the `1. Look in the arguments list and put the list of arguments in between {}
        ///
        /// Example: Hashtable{`0}.KeyValuePair 
        /// </summary>
        private IEnumerable<string> GetGenericDisplayNames(IEnumerable<string> displayNames)
        {
            // The most outputs when run on mscorlib are under 50 bytes in length
            const int SB_CAPACITY = 50;

            int index = 0;
            foreach (var displayName in displayNames)
            {
                int pos = displayName.IndexOf('`');
                if (pos <= 0)
                {
                    yield return displayName;
                    continue;
                }

                // Separate the name from the generic marker and the string after.
                // The format is: 'StringBefore`10StringAfter'
                int numGenericArgs;
                int offsetStringAfterGenericMarker;

                if (!CalculateGenericArgsOffset(displayName, pos, out numGenericArgs, out offsetStringAfterGenericMarker))
                {
                    yield return displayName;
                    continue;
                }

                Debug.Assert(index + numGenericArgs <= GenericTypeArgs.Count, "index + numGenericArgs is too large");

                // Start with the name up to the generic parameter token
                var sb = new StringBuilder(displayName, 0, pos, SB_CAPACITY);

                // Add the generic parameters
                sb.Append("{");
                sb.Append(string.Join(",", GenericTypeArgs.GetRange(index, numGenericArgs)));
                sb.Append("}");

                // Add any part that was after the generic entry 
                if (displayName.Length > offsetStringAfterGenericMarker)
                {
                    var length = displayName.Length - offsetStringAfterGenericMarker;

                    sb.Append(displayName, offsetStringAfterGenericMarker, length);
                }
<commit_msg>Add support for a corner case where nested generic types are referenced and only the outer type has generic arguments supplied.<commit_after>﻿// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection.Metadata;
using System.Text;

namespace Microsoft.Fx.Portability.Analyzer
{
    internal class MemberMetadataInfo
    {
        public MemberMetadataInfo()
        {
            Kind = MemberKind.Type;
            Names = new List<string>();
        }

        public MemberMetadataInfo(MemberMetadataInfo other)
        {
            Names = other.Names;
            MethodSignature = other.MethodSignature;
            Module = other.Module;
            IsArrayType = other.IsArrayType;
            ArrayTypeInfo = other.ArrayTypeInfo;
            IsTypeDef = other.IsTypeDef;
            IsPrimitiveType = other.IsPrimitiveType;
            IsFunctionPointer = other.IsFunctionPointer;
            Kind = other.Kind;
            IsGenericInstance = other.IsGenericInstance;
            IsEnclosedType = other.IsEnclosedType;
            Name = other.Name;
            Namespace = other.Namespace;
            DefinedInAssembly = other.DefinedInAssembly;
            IsAssemblySet = other.IsAssemblySet;

            if (other.ParentType != null)
            {
                ParentType = new MemberMetadataInfo(other.ParentType);
            }

            if (other.GenericTypeArgs != null)
            {
                GenericTypeArgs = other.GenericTypeArgs.Select(o => new MemberMetadataInfo(o)).ToList();
            }
        }

        public MemberMetadataInfo(MemberMetadataInfo other1, MemberMetadataInfo other2)
            : this(other1)
        {
            Names.AddRange(other2.Names);
            Names.Add(other2.Name);

            if (other2.Namespace != null)
            {
                Namespace = other2.Namespace;
            }

            if (other2.IsAssemblySet)
            {
                DefinedInAssembly = other2.DefinedInAssembly;
                IsAssemblySet = true;
            }
        }

        public List<string> Names { get; set; }

        public MethodSignature<MemberMetadataInfo> MethodSignature { get; set; }

        public ModuleReferenceHandle Module { get; set; }

        public List<MemberMetadataInfo> GenericTypeArgs { get; set; }

        public bool IsArrayType { get; set; }

        public string ArrayTypeInfo { get; set; }

        public bool IsTypeDef { get; set; }

        public bool IsPrimitiveType { get; set; }

        public bool IsFunctionPointer { get; set; }

        public MemberKind Kind { get; set; }

        public bool IsGenericInstance { get; set; }

        public bool IsEnclosedType { get; set; }

        public string Name { get; set; }

        public string Namespace { get; set; }

        public MemberMetadataInfo ParentType { get; set; }

        public AssemblyReference DefinedInAssembly { get; set; }

        public bool IsAssemblySet { get; set; }

        public override string ToString()
        {
            if (Kind == MemberKind.Method || Kind == MemberKind.Field)
            {
                return GenerateMemberDocId();
            }
            else
            {
                return GenerateTypeDocId();
            }
        }

        private string GenerateTypeDocId()
        {
            var sb = new StringBuilder();

            if (Namespace != null)
            {
                sb.Append(Namespace);
                sb.Append(".");
            }

            var combinedNames = new List<string>(Names) { Name };
            var displayNames = IsGenericInstance && GenericTypeArgs != null && IsEnclosedType
                ? GetGenericDisplayNames(combinedNames) : combinedNames;

            sb.Append(string.Join(".", displayNames));

            return sb.ToString();
        }

        /// <summary>
        /// Add the type arguments for generic instances. Go through all type names, and if it is generic, such 
        /// as Hashtable`1, remove the `1. Look in the arguments list and put the list of arguments in between {}
        ///
        /// Example: Hashtable{`0}.KeyValuePair 
        /// 
        /// There are some interesting corner cases involving nested types -
        /// First, generic argument indexes are counted over all types in the nested type hierarchy.
        /// Therefore, OuterClass`2.InnerClass`2 should resolve as OuterClass{`0,`1}.InnerClass{`2,`3}.
        /// 
        /// Secondly, it is not required that all generic arguments be made concrete.
        /// For example, it's possible in IL to define nested generic types OuterClass`2.InnerClass`2 and
        /// then pass only two generic types in GenericTypeArgs. In such cases, the type should resolve
        /// as OuterClass{`0,`1}.InnerClass`2.
        /// </summary>
        private IEnumerable<string> GetGenericDisplayNames(IEnumerable<string> displayNames)
        {
            // The most outputs when run on mscorlib are under 50 bytes in length
            const int SB_CAPACITY = 50;

            // Index goes outside the for loop because it increments over all type names, not just a specific type name
            int index = 0;
            foreach (var displayName in displayNames)
            {
                int pos = displayName.IndexOf('`');
                if (pos <= 0)
                {
                    yield return displayName;
                    continue;
                }

                // Separate the name from the generic marker and the string after.
                // The format is: 'StringBefore`10StringAfter'
                int numGenericArgs;
                int offsetStringAfterGenericMarker;

                // This calculates the number of generic arguments in the specific displayName we're looking at in
                // this iteration of the foreach loop.
                if (!CalculateGenericArgsOffset(displayName, pos, out numGenericArgs, out offsetStringAfterGenericMarker))
                {
                    yield return displayName;
                    continue;
                }

                // It is possible (from IL) to only pass generic type arguments for the outer class in the case of nested generic classes.
                // In such cases, once we run out of generic arguments, we just return the generic type names without resolving generic arguments.
                if (index + numGenericArgs > GenericTypeArgs.Count)
                {
                    yield return displayName;
                    continue;
                }

                // Start with the name up to the generic parameter token
                var sb = new StringBuilder(displayName, 0, pos, SB_CAPACITY);

                // Add the generic parameters
                sb.Append("{");
                sb.Append(string.Join(",", GenericTypeArgs.GetRange(index, numGenericArgs)));
                sb.Append("}");

                // Add any part that was after the generic entry 
                if (displayName.Length > offsetStringAfterGenericMarker)
                {
                    var length = displayName.Length - offsetStringAfterGenericMarker;

                    sb.Append(displayName, offsetStringAfterGenericMarker, length);
                }

{'fraction_non_alphanumeric': 0.07749759668712564, 'fraction_numerical': 0.00391924868742143, 'mean_word_length': 1.7194852201890207, 'pattern_counts': {'":': 0, '<': 24, '<?xml version=': 0, '>': 26, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1088140', 'n_tokens_mistral': 3280, 'n_tokens_neox': 3091, 'n_words': 978}