Port CoreCLR's TypeNameBuilder to C#, and use it in Mono too (#33701)
authorAlexis Christoforides <alexis@thenull.net>
Fri, 3 Apr 2020 14:28:23 +0000 (10:28 -0400)
committerGitHub <noreply@github.com>
Fri, 3 Apr 2020 14:28:23 +0000 (10:28 -0400)
* Port CoreCLR's TypeNameBuilder to C#, and use it in Mono too

Mono's System.Reflection.Emit creates type names that fail to be normalized or shaped in all ways that CoreCLR does.

Port CoreCLR's mixed-mode thread-unsafe implementation to thread-safe C#, and start using it in Mono for names in TypeBuilder.

Fixes issues with e.g. null-delimited type names being passed to different Reflection.Emit builders. Contributes to https://github.com/dotnet/runtime/issues/2389.

src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj
src/coreclr/src/System.Private.CoreLib/src/System/Reflection/Emit/AQNBuilder.cs [deleted file]
src/coreclr/src/vm/ecalllist.h
src/coreclr/src/vm/typestring.cpp
src/coreclr/src/vm/typestring.h
src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/TypeNameBuilder.cs [new file with mode: 0644]
src/mono/netcore/System.Private.CoreLib/src/System/Reflection/Emit/TypeBuilder.Mono.cs

index a1c8ca7..c8a6bff 100644 (file)
     <Compile Include="$(BclSourcesRoot)\System\Reflection\Associates.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Reflection\ConstructorInfo.CoreCLR.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Reflection\CustomAttribute.cs" />
-    <Compile Include="$(BclSourcesRoot)\System\Reflection\Emit\AQNBuilder.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Reflection\Emit\AssemblyBuilder.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Reflection\Emit\AssemblyBuilderData.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Reflection\Emit\ConstructorBuilder.cs" />
diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Reflection/Emit/AQNBuilder.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Reflection/Emit/AQNBuilder.cs
deleted file mode 100644 (file)
index 61d2037..0000000
+++ /dev/null
@@ -1,158 +0,0 @@
-// 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.Collections.Generic;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-namespace System.Reflection.Emit
-{
-    // TypeNameBuilder is NOT thread safe NOR reliable
-    internal class TypeNameBuilder
-    {
-        internal enum Format
-        {
-            ToString,
-            FullName,
-            AssemblyQualifiedName,
-        }
-
-        #region QCalls
-        [DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
-        private static extern IntPtr CreateTypeNameBuilder();
-        [DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
-        private static extern void ReleaseTypeNameBuilder(IntPtr pAQN);
-        [DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
-        private static extern void OpenGenericArguments(IntPtr tnb);
-        [DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
-        private static extern void CloseGenericArguments(IntPtr tnb);
-        [DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
-        private static extern void OpenGenericArgument(IntPtr tnb);
-        [DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
-        private static extern void CloseGenericArgument(IntPtr tnb);
-        [DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
-        private static extern void AddName(IntPtr tnb, string name);
-        [DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
-        private static extern void AddPointer(IntPtr tnb);
-        [DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
-        private static extern void AddByRef(IntPtr tnb);
-        [DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
-        private static extern void AddSzArray(IntPtr tnb);
-        [DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
-        private static extern void AddArray(IntPtr tnb, int rank);
-        [DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
-        private static extern void AddAssemblySpec(IntPtr tnb, string assemblySpec);
-        [DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
-        private static extern void ToString(IntPtr tnb, StringHandleOnStack retString);
-        [DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
-        private static extern void Clear(IntPtr tnb);
-        #endregion
-
-        #region Static Members
-
-        // TypeNameBuilder is NOT thread safe NOR reliable
-        internal static string? ToString(Type type, Format format)
-        {
-            if (format == Format.FullName || format == Format.AssemblyQualifiedName)
-            {
-                if (!type.IsGenericTypeDefinition && type.ContainsGenericParameters)
-                    return null;
-            }
-
-            TypeNameBuilder tnb = new TypeNameBuilder(CreateTypeNameBuilder());
-            tnb.Clear();
-            tnb.ConstructAssemblyQualifiedNameWorker(type, format);
-            string? toString = tnb.ToString();
-            tnb.Dispose();
-            return toString;
-        }
-        #endregion
-
-        #region Private Data Members
-        private IntPtr m_typeNameBuilder;
-        #endregion
-
-        #region Constructor
-        private TypeNameBuilder(IntPtr typeNameBuilder) { m_typeNameBuilder = typeNameBuilder; }
-        internal void Dispose() { ReleaseTypeNameBuilder(m_typeNameBuilder); }
-        #endregion
-
-        #region private Members
-        private void AddElementType(Type elementType)
-        {
-            if (elementType.HasElementType)
-                AddElementType(elementType.GetElementType()!);
-
-            if (elementType.IsPointer)
-                AddPointer();
-            else if (elementType.IsByRef)
-                AddByRef();
-            else if (elementType.IsSZArray)
-                AddSzArray();
-            else if (elementType.IsArray)
-                AddArray(elementType.GetArrayRank());
-        }
-
-        private void ConstructAssemblyQualifiedNameWorker(Type type, Format format)
-        {
-            Type rootType = type;
-
-            while (rootType.HasElementType)
-                rootType = rootType.GetElementType()!;
-
-            // Append namespace + nesting + name
-            List<Type> nestings = new List<Type>();
-            for (Type? t = rootType; t != null; t = t.IsGenericParameter ? null : t.DeclaringType)
-                nestings.Add(t);
-
-            for (int i = nestings.Count - 1; i >= 0; i--)
-            {
-                Type enclosingType = nestings[i];
-                string name = enclosingType.Name;
-
-                if (i == nestings.Count - 1 && enclosingType.Namespace != null && enclosingType.Namespace.Length != 0)
-                    name = enclosingType.Namespace + "." + name;
-
-                AddName(name);
-            }
-
-            // Append generic arguments
-            if (rootType.IsGenericType && (!rootType.IsGenericTypeDefinition || format == Format.ToString))
-            {
-                Type[] genericArguments = rootType.GetGenericArguments();
-
-                OpenGenericArguments();
-                for (int i = 0; i < genericArguments.Length; i++)
-                {
-                    Format genericArgumentsFormat = format == Format.FullName ? Format.AssemblyQualifiedName : format;
-
-                    OpenGenericArgument();
-                    ConstructAssemblyQualifiedNameWorker(genericArguments[i], genericArgumentsFormat);
-                    CloseGenericArgument();
-                }
-                CloseGenericArguments();
-            }
-
-            // Append pointer, byRef and array qualifiers
-            AddElementType(type);
-
-            if (format == Format.AssemblyQualifiedName)
-                AddAssemblySpec(type.Module.Assembly.FullName!);
-        }
-
-        private void OpenGenericArguments() { OpenGenericArguments(m_typeNameBuilder); }
-        private void CloseGenericArguments() { CloseGenericArguments(m_typeNameBuilder); }
-        private void OpenGenericArgument() { OpenGenericArgument(m_typeNameBuilder); }
-        private void CloseGenericArgument() { CloseGenericArgument(m_typeNameBuilder); }
-        private void AddName(string name) { AddName(m_typeNameBuilder, name); }
-        private void AddPointer() { AddPointer(m_typeNameBuilder); }
-        private void AddByRef() { AddByRef(m_typeNameBuilder); }
-        private void AddSzArray() { AddSzArray(m_typeNameBuilder); }
-        private void AddArray(int rank) { AddArray(m_typeNameBuilder, rank); }
-        private void AddAssemblySpec(string assemblySpec) { AddAssemblySpec(m_typeNameBuilder, assemblySpec); }
-        public override string? ToString() { string? ret = null; ToString(m_typeNameBuilder, new StringHandleOnStack(ref ret)); return ret; }
-        private void Clear() { Clear(m_typeNameBuilder); }
-        #endregion
-    }
-}
index 7717ac3..78ca688 100644 (file)
@@ -434,24 +434,6 @@ FCFuncStart(gMdUtf8String)
     QCFuncElement("HashCaseInsensitive", MdUtf8String::HashCaseInsensitive)
 FCFuncEnd()
 
-FCFuncStart(gTypeNameBuilder)
-    QCFuncElement("CreateTypeNameBuilder", TypeNameBuilder::_CreateTypeNameBuilder)
-    QCFuncElement("ReleaseTypeNameBuilder", TypeNameBuilder::_ReleaseTypeNameBuilder)
-    QCFuncElement("OpenGenericArguments", TypeNameBuilder::_OpenGenericArguments)
-    QCFuncElement("CloseGenericArguments", TypeNameBuilder::_CloseGenericArguments)
-    QCFuncElement("OpenGenericArgument", TypeNameBuilder::_OpenGenericArgument)
-    QCFuncElement("CloseGenericArgument", TypeNameBuilder::_CloseGenericArgument)
-    QCFuncElement("AddName", TypeNameBuilder::_AddName)
-    QCFuncElement("AddPointer", TypeNameBuilder::_AddPointer)
-    QCFuncElement("AddByRef", TypeNameBuilder::_AddByRef)
-    QCFuncElement("AddSzArray", TypeNameBuilder::_AddSzArray)
-    QCFuncElement("AddArray", TypeNameBuilder::_AddArray)
-    QCFuncElement("AddAssemblySpec", TypeNameBuilder::_AddAssemblySpec)
-    QCFuncElement("ToString", TypeNameBuilder::_ToString)
-    QCFuncElement("Clear", TypeNameBuilder::_Clear)
-FCFuncEnd()
-
-
 FCFuncStart(gSafeTypeNameParserHandle)
     QCFuncElement("_ReleaseTypeNameParser", TypeName::QReleaseTypeNameParser)
 FCFuncEnd()
@@ -1315,7 +1297,6 @@ FCClassElement("TimerQueue", "System.Threading", gTimerFuncs)
 FCClassElement("Type", "System", gSystem_Type)
 FCClassElement("TypeBuilder", "System.Reflection.Emit", gCOMClassWriter)
 FCClassElement("TypeLoadException", "System", gTypeLoadExceptionFuncs)
-FCClassElement("TypeNameBuilder", "System.Reflection.Emit", gTypeNameBuilder)
 FCClassElement("TypeNameParser", "System", gTypeNameParser)
 FCClassElement("TypedReference", "System", gTypedReferenceFuncs)
 #ifdef FEATURE_COMINTEROP
index ff4f4a7..07d60e0 100644 (file)
 #include "ex.h"
 #include "typedesc.h"
 
-#if !defined(DACCESS_COMPILE) && !defined(CROSSGEN_COMPILE)
-TypeNameBuilder * QCALLTYPE TypeNameBuilder::_CreateTypeNameBuilder()
-{
-    QCALL_CONTRACT;
-
-    TypeNameBuilder * retVal = NULL;
-    BEGIN_QCALL;
-    retVal = new TypeNameBuilder();
-    END_QCALL;
-
-    return retVal;
-}
-
-void QCALLTYPE TypeNameBuilder::_ReleaseTypeNameBuilder(TypeNameBuilder * pTnb)
-{
-    QCALL_CONTRACT;
-
-    BEGIN_QCALL;
-    delete pTnb;
-    END_QCALL;
-}
-
-void QCALLTYPE TypeNameBuilder::_ToString(TypeNameBuilder * pTnb, QCall::StringHandleOnStack retString)
-{
-    QCALL_CONTRACT;
-
-    BEGIN_QCALL;
-    retString.Set(*pTnb->GetString());
-    END_QCALL;
-}
-
-void QCALLTYPE TypeNameBuilder::_AddName(TypeNameBuilder * pTnb, LPCWSTR wszName)
-{
-    QCALL_CONTRACT;
-
-    BEGIN_QCALL;
-    pTnb->AddName(wszName);
-    END_QCALL;
-}
-
-void QCALLTYPE TypeNameBuilder::_AddAssemblySpec(TypeNameBuilder * pTnb, LPCWSTR wszAssemblySpec)
-{
-    QCALL_CONTRACT;
-
-    BEGIN_QCALL;
-    pTnb->AddAssemblySpec(wszAssemblySpec);
-    END_QCALL;
-}
-
-void QCALLTYPE TypeNameBuilder::_OpenGenericArguments(TypeNameBuilder * pTnb)
-{
-    QCALL_CONTRACT;
-
-    BEGIN_QCALL;
-    pTnb->OpenGenericArguments();
-    END_QCALL;
-}
-
-void QCALLTYPE TypeNameBuilder::_CloseGenericArguments(TypeNameBuilder * pTnb)
-{
-    QCALL_CONTRACT;
-
-    BEGIN_QCALL;
-    pTnb->CloseGenericArguments();
-    END_QCALL;
-}
-
-void QCALLTYPE TypeNameBuilder::_OpenGenericArgument(TypeNameBuilder * pTnb)
-{
-    QCALL_CONTRACT;
-
-    BEGIN_QCALL;
-    pTnb->OpenGenericArgument();
-    END_QCALL;
-}
-
-void QCALLTYPE TypeNameBuilder::_CloseGenericArgument(TypeNameBuilder * pTnb)
-{
-    QCALL_CONTRACT;
-
-    BEGIN_QCALL;
-    pTnb->CloseGenericArgument();
-    END_QCALL;
-}
-
-void QCALLTYPE TypeNameBuilder::_AddPointer(TypeNameBuilder * pTnb)
-{
-    QCALL_CONTRACT;
-
-    BEGIN_QCALL;
-    pTnb->AddPointer();
-    END_QCALL;
-}
-
-void QCALLTYPE TypeNameBuilder::_AddByRef(TypeNameBuilder * pTnb)
-{
-    QCALL_CONTRACT;
-
-    BEGIN_QCALL;
-    pTnb->AddByRef();
-    END_QCALL;
-}
-
-void QCALLTYPE TypeNameBuilder::_AddSzArray(TypeNameBuilder * pTnb)
-{
-    QCALL_CONTRACT;
-
-    BEGIN_QCALL;
-    pTnb->AddSzArray();
-    END_QCALL;
-}
-
-void QCALLTYPE TypeNameBuilder::_AddArray(TypeNameBuilder * pTnb, DWORD dwRank)
-{
-    QCALL_CONTRACT;
-
-    BEGIN_QCALL;
-    pTnb->AddArray(dwRank);
-    END_QCALL;
-}
-
-void QCALLTYPE TypeNameBuilder::_Clear(TypeNameBuilder * pTnb)
-{
-    QCALL_CONTRACT;
-
-    BEGIN_QCALL;
-    pTnb->Clear();
-    END_QCALL;
-}
-
-#endif
-
 //
 // TypeNameBuilder
 //
index 7e5bdf3..c259e43 100644 (file)
@@ -32,22 +32,6 @@ class TypeString;
 
 class TypeNameBuilder
 {
-public:
-    static void QCALLTYPE _ReleaseTypeNameBuilder(TypeNameBuilder * pTnb);
-    static TypeNameBuilder * QCALLTYPE _CreateTypeNameBuilder();
-    static void QCALLTYPE _OpenGenericArguments(TypeNameBuilder * pTnb);
-    static void QCALLTYPE _CloseGenericArguments(TypeNameBuilder *pTnb);
-    static void QCALLTYPE _OpenGenericArgument(TypeNameBuilder * pTnb);
-    static void QCALLTYPE _CloseGenericArgument(TypeNameBuilder * pTnb);
-    static void QCALLTYPE _AddName(TypeNameBuilder * pTnb, LPCWSTR wszName);
-    static void QCALLTYPE _AddPointer(TypeNameBuilder * pTnb);
-    static void QCALLTYPE _AddByRef(TypeNameBuilder * pTnb);
-    static void QCALLTYPE _AddSzArray(TypeNameBuilder * pTnb);
-    static void QCALLTYPE _AddArray(TypeNameBuilder * pTnb, DWORD dwRank);
-    static void QCALLTYPE _AddAssemblySpec(TypeNameBuilder * pTnb, LPCWSTR wszAssemblySpec);
-    static void QCALLTYPE _ToString(TypeNameBuilder * pTnb, QCall::StringHandleOnStack retString);
-    static void QCALLTYPE _Clear(TypeNameBuilder * pTnb);
-
 private:
     friend class TypeString;
     friend SString* TypeName::ToString(SString*, BOOL, BOOL, BOOL);
index 689c615..4e8a39b 100644 (file)
     <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\Emit\SignatureToken.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\Emit\StackBehaviour.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\Emit\StringToken.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\Emit\TypeNameBuilder.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\Emit\TypeToken.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\EventAttributes.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Reflection\EventInfo.cs" />
diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/TypeNameBuilder.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/TypeNameBuilder.cs
new file mode 100644 (file)
index 0000000..5f9bb76
--- /dev/null
@@ -0,0 +1,347 @@
+// 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.
+//
+//  This TypeNameBuilder is ported from CoreCLR's original.
+//  It replaces the C++ bits of the implementation with a faithful C# port.
+
+using System.Collections.Generic;
+using System.Collections;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Diagnostics;
+
+namespace System.Reflection.Emit
+{
+    internal class TypeNameBuilder
+    {
+        private StringBuilder _str = new StringBuilder();
+        private int _instNesting;
+        private bool _firstInstArg;
+        private bool _nestedName;
+        private bool _hasAssemblySpec;
+        private bool _useAngleBracketsForGenerics;
+        private List<int> _stack = new List<int>();
+        private int _stackIdx;
+
+        private TypeNameBuilder()
+        {
+        }
+
+        private void OpenGenericArguments()
+        {
+            _instNesting++;
+            _firstInstArg = true;
+
+            if (_useAngleBracketsForGenerics)
+                Append('<');
+            else
+                Append('[');
+        }
+
+        private void CloseGenericArguments()
+        {
+            Debug.Assert(_instNesting != 0);
+
+            _instNesting--;
+
+            if (_firstInstArg)
+            {
+                _str.Remove(_str.Length - 1, 1);
+            }
+            else
+            {
+                if (_useAngleBracketsForGenerics)
+                    Append('>');
+                else
+                    Append(']');
+            }
+        }
+
+        private void OpenGenericArgument()
+        {
+            Debug.Assert(_instNesting != 0);
+
+            _nestedName = false;
+
+            if (!_firstInstArg)
+                Append(',');
+
+            _firstInstArg = false;
+
+            if (_useAngleBracketsForGenerics)
+                Append('<');
+            else
+                Append('[');
+
+            PushOpenGenericArgument();
+        }
+
+        private void CloseGenericArgument()
+        {
+            Debug.Assert(_instNesting != 0);
+
+            if (_hasAssemblySpec)
+            {
+                if (_useAngleBracketsForGenerics)
+                    Append('>');
+                else
+                    Append(']');
+            }
+
+            PopOpenGenericArgument();
+        }
+
+        private void AddName(string name)
+        {
+            Debug.Assert(name != null);
+
+            if (_nestedName)
+                Append('+');
+
+            _nestedName = true;
+
+            EscapeName(name);
+        }
+
+        private void AddArray(int rank)
+        {
+            Debug.Assert(rank > 0);
+
+            if (rank == 1)
+            {
+                Append("[*]");
+            }
+            else if (rank > 64)
+            {
+                // Only taken in an error path, runtime will not load arrays of more than 32 dimensions
+                _str.Append('[').Append(rank).Append(']');
+            }
+            else
+            {
+                Append('[');
+                for (int i = 1; i < rank; i++)
+                    Append(',');
+                Append(']');
+            }
+        }
+
+        private void AddAssemblySpec(string assemblySpec)
+        {
+            if (assemblySpec != null && !assemblySpec.Equals(""))
+            {
+                Append(", ");
+
+                if (_instNesting > 0)
+                {
+                    EscapeEmbeddedAssemblyName(assemblySpec);
+                }
+                else
+                {
+                    EscapeAssemblyName(assemblySpec);
+                }
+
+                _hasAssemblySpec = true;
+            }
+        }
+
+        public override string ToString()
+        {
+            Debug.Assert(_instNesting == 0);
+
+            return _str.ToString();
+        }
+
+        private static bool ContainsReservedChar(string name)
+        {
+            foreach (char c in name)
+            {
+                if (c == '\0')
+                    break;
+                if (IsTypeNameReservedChar(c))
+                    return true;
+            }
+            return false;
+        }
+
+        private static bool IsTypeNameReservedChar(char ch)
+        {
+            switch (ch)
+            {
+                case ',':
+                case '[':
+                case ']':
+                case '&':
+                case '*':
+                case '+':
+                case '\\':
+                    return true;
+
+                default:
+                    return false;
+            }
+        }
+
+        private void EscapeName(string name)
+        {
+            if (ContainsReservedChar(name))
+            {
+                foreach (char c in name)
+                {
+                    if (c == '\0')
+                        break;
+                    if (IsTypeNameReservedChar(c))
+                        _str.Append('\\');
+                    _str.Append(c);
+                }
+            }
+            else
+                Append(name);
+        }
+
+        private void EscapeAssemblyName(string name)
+        {
+            Append(name);
+        }
+
+        private void EscapeEmbeddedAssemblyName(string name)
+        {
+            if (name.Contains(']'))
+            {
+                foreach (char c in name)
+                {
+                    if (c == ']')
+                        Append('\\');
+
+                    Append(c);
+                }
+            }
+            else
+            {
+                Append(name);
+            }
+        }
+
+        private void PushOpenGenericArgument()
+        {
+            _stack.Add(_str.Length);
+            _stackIdx++;
+        }
+
+        private void PopOpenGenericArgument()
+        {
+            int index = _stack[--_stackIdx];
+            _stack.RemoveAt(_stackIdx);
+
+            if (!_hasAssemblySpec)
+                _str.Remove(index - 1, 1);
+
+            _hasAssemblySpec = false;
+        }
+
+        private void SetUseAngleBracketsForGenerics(bool value)
+        {
+            _useAngleBracketsForGenerics = value;
+        }
+
+        private void Append(string pStr)
+        {
+            foreach (char c in pStr)
+            {
+                if (c == '\0')
+                    break;
+                _str.Append(c);
+            }
+        }
+
+        private void Append(char c)
+        {
+            _str.Append(c);
+        }
+
+        internal enum Format
+        {
+            ToString,
+            FullName,
+            AssemblyQualifiedName,
+        }
+
+        internal static string? ToString(Type type, Format format)
+        {
+            if (format == Format.FullName || format == Format.AssemblyQualifiedName)
+            {
+                if (!type.IsGenericTypeDefinition && type.ContainsGenericParameters)
+                    return null;
+            }
+
+            var tnb = new TypeNameBuilder();
+            tnb.AddAssemblyQualifiedName(type, format);
+            return tnb.ToString();
+        }
+
+        private void AddElementType(Type type)
+        {
+            if (!type.HasElementType)
+                return;
+
+            AddElementType(type.GetElementType()!);
+
+            if (type.IsPointer)
+                Append('*');
+            else if (type.IsByRef)
+                Append('&');
+            else if (type.IsSZArray)
+                Append("[]");
+            else if (type.IsArray)
+                AddArray(type.GetArrayRank());
+        }
+
+        private void AddAssemblyQualifiedName(Type type, Format format)
+        {
+            Type rootType = type;
+
+            while (rootType.HasElementType)
+                rootType = rootType.GetElementType()!;
+
+            // Append namespace + nesting + name
+            var nestings = new List<Type>();
+            for (Type? t = rootType; t != null; t = t.IsGenericParameter ? null : t.DeclaringType)
+                nestings.Add(t);
+
+            for (int i = nestings.Count - 1; i >= 0; i--)
+            {
+                Type enclosingType = nestings[i];
+                string name = enclosingType.Name;
+
+                if (i == nestings.Count - 1 && enclosingType.Namespace != null && enclosingType.Namespace.Length != 0)
+                    name = enclosingType.Namespace + "." + name;
+
+                AddName(name);
+            }
+
+            // Append generic arguments
+            if (rootType.IsGenericType && (!rootType.IsGenericTypeDefinition || format == Format.ToString))
+            {
+                Type[] genericArguments = rootType.GetGenericArguments();
+
+                OpenGenericArguments();
+                for (int i = 0; i < genericArguments.Length; i++)
+                {
+                    Format genericArgumentsFormat = format == Format.FullName ? Format.AssemblyQualifiedName : format;
+
+                    OpenGenericArgument();
+                    AddAssemblyQualifiedName(genericArguments[i], genericArgumentsFormat);
+                    CloseGenericArgument();
+                }
+                CloseGenericArguments();
+            }
+
+            // Append pointer, byRef and array qualifiers
+            AddElementType(type);
+
+            if (format == Format.AssemblyQualifiedName)
+                AddAssemblySpec(type.Module.Assembly.FullName!);
+        }
+    }
+}
index 872c3c7..d50c2cd 100644 (file)
@@ -144,7 +144,7 @@ namespace System.Reflection.Emit
         {
             get
             {
-                return fullname.DisplayName + ", " + Assembly.FullName;
+                return TypeNameBuilder.ToString(this, TypeNameBuilder.Format.AssemblyQualifiedName);
             }
         }
 
@@ -212,7 +212,7 @@ namespace System.Reflection.Emit
         {
             get
             {
-                return fullname.DisplayName;
+                return TypeNameBuilder.ToString(this, TypeNameBuilder.Format.FullName);
             }
         }
 
@@ -826,19 +826,19 @@ namespace System.Reflection.Emit
             if (parent != null)
             {
                 if (parent.IsSealed)
-                    throw new TypeLoadException("Could not load type '" + FullName + "' from assembly '" + Assembly + "' because the parent type is sealed.");
+                    throw new TypeLoadException("Could not load type '" + fullname.DisplayName + "' from assembly '" + Assembly + "' because the parent type is sealed.");
                 if (parent.IsGenericTypeDefinition)
                     throw new BadImageFormatException();
             }
 
             if (parent == typeof(Enum) && methods != null)
-                throw new TypeLoadException("Could not load type '" + FullName + "' from assembly '" + Assembly + "' because it is an enum with methods.");
+                throw new TypeLoadException("Could not load type '" + fullname.DisplayName + "' from assembly '" + Assembly + "' because it is an enum with methods.");
             if (interfaces != null)
             {
                 foreach (Type iface in interfaces)
                 {
                     if (iface.IsNestedPrivate && iface.Assembly != Assembly)
-                        throw new TypeLoadException("Could not load type '" + FullName + "' from assembly '" + Assembly + "' because it is implements the inaccessible interface '" + iface.FullName + "'.");
+                        throw new TypeLoadException("Could not load type '" + fullname.DisplayName + "' from assembly '" + Assembly + "' because it is implements the inaccessible interface '" + iface.FullName + "'.");
                     if (iface.IsGenericTypeDefinition)
                         throw new BadImageFormatException();
                     if (!iface.IsInterface)