// 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 disable

using System;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.ExpressionEvaluator;
using Microsoft.VisualStudio.Debugger.ComponentInterfaces;
using Microsoft.VisualStudio.Debugger.Clr;
using Microsoft.VisualStudio.Debugger.Evaluation;
using Roslyn.Test.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator.UnitTests
{
    public class FullNameTests : CSharpResultProviderTestBase
    {
        [Fact]
        public void Null()
        {
            IDkmClrFullNameProvider fullNameProvider = new CSharpFormatter();
            var inspectionContext = CreateDkmInspectionContext();
            Assert.Equal("null", fullNameProvider.GetClrExpressionForNull(inspectionContext));
        }

        [Fact]
        public void This()
        {
            IDkmClrFullNameProvider fullNameProvider = new CSharpFormatter();
            var inspectionContext = CreateDkmInspectionContext();
            Assert.Equal("this", fullNameProvider.GetClrExpressionForThis(inspectionContext));
        }

        [Fact]
        public void ArrayIndex()
        {
            IDkmClrFullNameProvider fullNameProvider = new CSharpFormatter();
            var inspectionContext = CreateDkmInspectionContext();
            Assert.Equal("[]", fullNameProvider.GetClrArrayIndexExpression(inspectionContext, new string[0]));
            Assert.Equal("[]", fullNameProvider.GetClrArrayIndexExpression(inspectionContext, new[] { "" }));
            Assert.Equal("[ ]", fullNameProvider.GetClrArrayIndexExpression(inspectionContext, new[] { " " }));
            Assert.Equal("[1]", fullNameProvider.GetClrArrayIndexExpression(inspectionContext, new[] { "1" }));
            Assert.Equal("[[], 2, 3]", fullNameProvider.GetClrArrayIndexExpression(inspectionContext, new[] { "[]", "2", "3" }));
            Assert.Equal("[, , ]", fullNameProvider.GetClrArrayIndexExpression(inspectionContext, new[] { "", "", "" }));
        }

        [Fact]
        public void Cast()
        {
            var source =
@"class C
{
}";
            var runtime = new DkmClrRuntimeInstance(ReflectionUtilities.GetMscorlib(GetAssembly(source)));
            using (runtime.Load())
            {
                IDkmClrFullNameProvider fullNameProvider = new CSharpFormatter();
                var inspectionContext = CreateDkmInspectionContext();
                var type = runtime.GetType("C");

                Assert.Equal("(C)o", fullNameProvider.GetClrCastExpression(inspectionContext, "o", type, null, DkmClrCastExpressionOptions.None));
                Assert.Equal("o as C", fullNameProvider.GetClrCastExpression(inspectionContext, "o", type, null, DkmClrCastExpressionOptions.ConditionalCast));
                Assert.Equal("(C)(o)", fullNameProvider.GetClrCastExpression(inspectionContext, "o", type, null, DkmClrCastExpressionOptions.ParenthesizeArgument));
                Assert.Equal("(o) as C", fullNameProvider.GetClrCastExpression(inspectionContext, "o", type, null, DkmClrCastExpressionOptions.ParenthesizeArgument | DkmClrCastExpressionOptions.ConditionalCast));
                Assert.Equal("((C)o)", fullNameProvider.GetClrCastExpression(inspectionContext, "o", type, null, DkmClrCastExpressionOptions.ParenthesizeEntireExpression));
                Assert.Equal("(o as C)", fullNameProvider.GetClrCastExpression(inspectionContext, "o", type, null, DkmClrCastExpressionOptions.ParenthesizeEntireExpression | DkmClrCastExpressionOptions.ConditionalCast));
                Assert.Equal("((C)(o))", fullNameProvider.GetClrCastExpression(inspectionContext, "o", type, null, DkmClrCastExpressionOptions.ParenthesizeEntireExpression | DkmClrCastExpressionOptions.ParenthesizeArgument));
                Assert.Equal("((o) as C)", fullNameProvider.GetClrCastExpression(inspectionContext, "o", type, null, DkmClrCastExpressionOptions.ParenthesizeEntireExpression | DkmClrCastExpressionOptions.ParenthesizeArgument | DkmClrCastExpressionOptions.ConditionalCast));

                // Some of the same tests with "..." as the expression ("..." is used
                // by the debugger when the expression cannot be determined).
                Assert.Equal("(C)...", fullNameProvider.GetClrCastExpression(inspectionContext, "...", type, null, DkmClrCastExpressionOptions.None));
                Assert.Equal("... as C", fullNameProvider.GetClrCastExpression(inspectionContext, "...", type, null, DkmClrCastExpressionOptions.ConditionalCast));
                Assert.Equal("(... as C)", fullNameProvider.GetClrCastExpression(inspectionContext, "...", type, null, DkmClrCastExpressionOptions.ParenthesizeEntireExpression | DkmClrCastExpressionOptions.ConditionalCast));
            }
        }

        [Fact]
        public void RootComment()
        {
            var source = @"
class C
{
    public int F;
}
";
            var assembly = GetAssembly(source);
            var type = assembly.GetType("C");
            var value = CreateDkmClrValue(type.Instantiate());

            var root = FormatResult("a // Comment", value);
            Assert.Equal("a.F", GetChildren(root).Single().FullName);

            root = FormatResult(" a // Comment", value);
            Assert.Equal("a.F", GetChildren(root).Single().FullName);

            root = FormatResult("a// Comment", value);
            Assert.Equal("a.F", GetChildren(root).Single().FullName);

            root = FormatResult("a /*b*/ +c /*d*/// Comment", value);
            Assert.Equal("(a  +c).F", GetChildren(root).Single().FullName);

            root = FormatResult("a /*//*/+ c// Comment", value);
            Assert.Equal("(a + c).F", GetChildren(root).Single().FullName);

            root = FormatResult("a /*/**/+ c// Comment", value);
            Assert.Equal("(a + c).F", GetChildren(root).Single().FullName);

            root = FormatResult("/**/a// Comment", value);
            Assert.Equal("a.F", GetChildren(root).Single().FullName);

            // See https://dev.azure.com/devdiv/DevDiv/_workitems/edit/847849
            root = FormatResult(@"""a//b/*"" // c", value);
            Assert.Equal(@"(""a//b/*"").F", GetChildren(root).Single().FullName);

            // incorrect - see https://github.com/dotnet/roslyn/issues/37536 
            root = FormatResult(@"""a"" //""b", value);
            Assert.Equal(@"(""a"" //""b).F", GetChildren(root).Single().FullName);
        }

        [Fact]
        public void RootFormatSpecifiers()
        {
            var source = @"
class C
{
    public int F;
}
";
            var assembly = GetAssembly(source);
            var type = assembly.GetType("C");
            var value = CreateDkmClrValue(type.Instantiate());

            var root = FormatResult("a, raw", value); // simple
            Assert.Equal("a, raw", root.FullName);
            Assert.Equal("a.F", GetChildren(root).Single().FullName);

            root = FormatResult("a, raw, ac, h", value); // multiple specifiers
            Assert.Equal("a, raw, ac, h", root.FullName);
            Assert.Equal("a.F", GetChildren(root).Single().FullName);

            root = FormatResult("M(a, b), raw", value); // non-specifier comma
            Assert.Equal("M(a, b), raw", root.FullName);
            Assert.Equal("M(a, b).F", GetChildren(root).Single().FullName);

            root = FormatResult("a, raw1", value); // alpha-numeric
            Assert.Equal("a, raw1", root.FullName);
            Assert.Equal("a.F", GetChildren(root).Single().FullName);

            root = FormatResult("a, $raw", value); // other punctuation
            Assert.Equal("a, $raw", root.FullName);
            Assert.Equal("(a, $raw).F", GetChildren(root).Single().FullName); // not ideal
        }

        [Fact]
        public void RootParentheses()
        {
            var source = @"
class C
{
    public int F;
}
";
            var assembly = GetAssembly(source);
            var type = assembly.GetType("C");
            var value = CreateDkmClrValue(type.Instantiate());

            var root = FormatResult("a + b", value);
            Assert.Equal("(a + b).F", GetChildren(root).Single().FullName); // required

            root = FormatResult("new C()", value);
            Assert.Equal("(new C()).F", GetChildren(root).Single().FullName); // documentation

            root = FormatResult("A.B", value);
            Assert.Equal("A.B.F", GetChildren(root).Single().FullName); // desirable

            root = FormatResult("A::B", value);
            Assert.Equal("(A::B).F", GetChildren(root).Single().FullName); // documentation
        }

        [Fact]
        public void RootTrailingSemicolons()
        {
            var source = @"
class C
{
    public int F;
}
";
            var assembly = GetAssembly(source);
            var type = assembly.GetType("C");
            var value = CreateDkmClrValue(type.Instantiate());

            var root = FormatResult("a;", value);
            Assert.Equal("a.F", GetChildren(root).Single().FullName);

            root = FormatResult("a + b;;", value);
            Assert.Equal("(a + b).F", GetChildren(root).Single().FullName);

            root = FormatResult(" M( ) ; ;", value);
            Assert.Equal("M( ).F", GetChildren(root).Single().FullName);
        }

        [Fact]
        public void RootMixedExtras()
        {
            var source = @"
class C
{
    public int F;
}
";
            var assembly = GetAssembly(source);
            var type = assembly.GetType("C");
            var value = CreateDkmClrValue(type.Instantiate());

            // Semicolon, then comment.
            var root = FormatResult("a; //", value);
            Assert.Equal("a", root.FullName);

            // Comment, then semicolon.
            root = FormatResult("a // ;", value);
            Assert.Equal("a", root.FullName);

            // Semicolon, then format specifier.
            root = FormatResult("a;, ac", value);
            Assert.Equal("a, ac", root.FullName);

            // Format specifier, then semicolon.
            root = FormatResult("a, ac;", value);
            Assert.Equal("a, ac", root.FullName);

            // Comment, then format specifier.
            root = FormatResult("a//, ac", value);
            Assert.Equal("a", root.FullName);

            // Format specifier, then comment.
            root = FormatResult("a, ac //", value);
            Assert.Equal("a, ac", root.FullName);

            // Everything.
            root = FormatResult("/*A*/ a /*B*/ + /*C*/ b /*D*/ ; ; , ac /*E*/, raw // ;, hidden", value);
            Assert.Equal("a  +  b, ac, raw", root.FullName);
        }

        [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1022165")]
        public void Keywords_Root()
        {
            var source = @"
class C
{
    void M()
    {
        int @namespace = 3;
    }
}
";
            var assembly = GetAssembly(source);
            var value = CreateDkmClrValue(3);

            var root = FormatResult("@namespace", value);
            Verify(root,
                EvalResult("@namespace", "3", "int", "@namespace"));

            value = CreateDkmClrValue(assembly.GetType("C").Instantiate());
            root = FormatResult("this", value);
            Verify(root,
                EvalResult("this", "{C}", "C", "this"));

            // Verify that keywords aren't escaped by the ResultProvider at the
            // root level (we would never expect to see "namespace" passed as a
            // resultName, but this check verifies that we leave them "as is").
            root = FormatResult("namespace", CreateDkmClrValue(new object()));
            Verify(root,
                EvalResult("namespace", "{object}", "object", "namespace"));
        }

        [Fact]
        public void Keywords_RuntimeType()
        {
            var source = @"
public class @struct
{
}

public class @namespace : @struct
{
    @struct m = new @if();
}

public class @if : @struct
{
}
";
            var assembly = GetAssembly(source);
            var type = assembly.GetType("namespace");
            var declaredType = assembly.GetType("struct");
            var value = CreateDkmClrValue(type.Instantiate(), type);

            var root = FormatResult("o", value, new DkmClrType((TypeImpl)declaredType));
            Verify(GetChildren(root),
                EvalResult("m", "{if}", "struct {if}", "((@namespace)o).m", DkmEvaluationResultFlags.CanFavorite));
        }

        [Fact]
        public void Keywords_ProxyType()
        {
            var source = @"
using System.Diagnostics;

[DebuggerTypeProxy(typeof(@class))]
public class @struct
{
    public bool @true = false;
}

public class @class
{
    public bool @false = true;

    public @class(@struct s) { }
}
";
            var assembly = GetAssembly(source);
            var value = CreateDkmClrValue(assembly.GetType("struct").Instantiate());

            var root = FormatResult("o", value);
            var children = GetChildren(root);
            Verify(children,
                EvalResult("@false", "true", "bool", "new @class(o).@false", DkmEvaluationResultFlags.Boolean | DkmEvaluationResultFlags.BooleanTrue),
                EvalResult("Raw View", null, "", "o, raw", DkmEvaluationResultFlags.Expandable | DkmEvaluationResultFlags.ReadOnly, DkmEvaluationResultCategory.Data));

            var grandChildren = GetChildren(children.Last());
            Verify(grandChildren,
                EvalResult("@true", "false", "bool", "o.@true", DkmEvaluationResultFlags.Boolean));
        }

        [Fact]
        public void Keywords_MemberAccess()
        {
            var source = @"
public class @struct
{
    public int @true;
}
";
            var assembly = GetAssembly(source);
            var value = CreateDkmClrValue(assembly.GetType("struct").Instantiate());

            var root = FormatResult("o", value);
            Verify(GetChildren(root),
                EvalResult("@true", "0", "int", "o.@true", DkmEvaluationResultFlags.CanFavorite));
        }

        [Fact]
        public void Keywords_StaticMembers()
        {
            var source = @"
public class @struct
{
    public static int @true;
}
";
            var assembly = GetAssembly(source);
            var value = CreateDkmClrValue(assembly.GetType("struct").Instantiate());

            var root = FormatResult("o", value);
            var children = GetChildren(root);
            Verify(children,
                EvalResult("Static members", null, "", "@struct", DkmEvaluationResultFlags.Expandable | DkmEvaluationResultFlags.ReadOnly, DkmEvaluationResultCategory.Class));
            Verify(GetChildren(children.Single()),
                EvalResult("@true", "0", "int", "@struct.@true", DkmEvaluationResultFlags.None, DkmEvaluationResultCategory.Data, DkmEvaluationResultAccessType.Public));
        }

        [Fact]
        public void Keywords_ExplicitInterfaceImplementation()
        {
            var source = @"
namespace @namespace
{
    public interface @interface<T>
    {
        int @return { get; set; }
    }

    public class @class : @interface<@class>
    {
        int @interface<@class>.@return { get; set; }
    }
}
";
            var assembly = GetAssembly(source);
            var value = CreateDkmClrValue(assembly.GetType("namespace.class").Instantiate());

            var root = FormatResult("instance", value);
            Verify(GetChildren(root),
                EvalResult("@namespace.@interface<@namespace.@class>.@return", "0", "int", "((@namespace.@interface<@namespace.@class>)instance).@return"));
        }

        [Fact]
        public void MangledNames_CastRequired()
        {
            var il = @"
.class public auto ansi beforefieldinit '<>Mangled' extends [mscorlib]System.Object
{
  .field public int32 x

  .method public hidebysig specialname rtspecialname instance void  .ctor() cil managed
  {
    ldarg.0
    call       instance void [mscorlib]System.Object::.ctor()
    ret
  }
}

.class public auto ansi beforefieldinit 'NotMangled' extends '<>Mangled'
{
  .field public int32 x

  .method public hidebysig specialname rtspecialname instance void  .ctor() cil managed
  {
    ldarg.0
    call       instance void '<>Mangled'::.ctor()
    ret
  }
}
";

            ImmutableArray<byte> assemblyBytes;
            ImmutableArray<byte> pdbBytes;
            CSharpTestBase.EmitILToArray(il, appendDefaultHeader: true, includePdb: false, assemblyBytes: out assemblyBytes, pdbBytes: out pdbBytes);
            var assembly = ReflectionUtilities.Load(assemblyBytes);

            var value = CreateDkmClrValue(assembly.GetType("NotMangled").Instantiate());

            var root = FormatResult("o", value);
            Verify(GetChildren(root),
                EvalResult("x (<>Mangled)", "0", "int", null),
                EvalResult("x", "0", "int", "o.x", DkmEvaluationResultFlags.CanFavorite));
        }

        [Fact]
        public void MangledNames_StaticMembers()
        {
            var il = @"
.class public auto ansi beforefieldinit '<>Mangled' extends [mscorlib]System.Object
{
  .field public static int32 x

  .method public hidebysig specialname rtspecialname instance void  .ctor() cil managed
  {
    ldarg.0
    call       instance void [mscorlib]System.Object::.ctor()
    ret
  }
}

.class public auto ansi beforefieldinit 'NotMangled' extends '<>Mangled'
{
  .field public static int32 y

  .method public hidebysig specialname rtspecialname instance void  .ctor() cil managed
  {
    ldarg.0
    call       instance void '<>Mangled'::.ctor()
    ret
  }
}
";

            ImmutableArray<byte> assemblyBytes;
            ImmutableArray<byte> pdbBytes;
            CSharpTestBase.EmitILToArray(il, appendDefaultHeader: true, includePdb: false, assemblyBytes: out assemblyBytes, pdbBytes: out pdbBytes);
            var assembly = ReflectionUtilities.Load(assemblyBytes);

            var baseValue = CreateDkmClrValue(assembly.GetType("<>Mangled").Instantiate());

            var root = FormatResult("o", baseValue);
            var children = GetChildren(root);
            Verify(children,
                EvalResult("Static members", null, "", null, DkmEvaluationResultFlags.Expandable | DkmEvaluationResultFlags.ReadOnly, DkmEvaluationResultCategory.Class));
            Verify(GetChildren(children.Single()),
                EvalResult("x", "0", "int", null));

            var derivedValue = CreateDkmClrValue(assembly.GetType("NotMangled").Instantiate());

            root = FormatResult("o", derivedValue);
            children = GetChildren(root);
            Verify(children,
                EvalResult("Static members", null, "", "NotMangled", DkmEvaluationResultFlags.Expandable | DkmEvaluationResultFlags.ReadOnly, DkmEvaluationResultCategory.Class));
            Verify(GetChildren(children.Single()),
                EvalResult("x", "0", "int", null, DkmEvaluationResultFlags.None, DkmEvaluationResultCategory.Data, DkmEvaluationResultAccessType.Public),
                EvalResult("y", "0", "int", "NotMangled.y", DkmEvaluationResultFlags.None, DkmEvaluationResultCategory.Data, DkmEvaluationResultAccessType.Public));
        }

        [Fact]
        public void MangledNames_ExplicitInterfaceImplementation()
        {
            var il = @"
.class interface public abstract auto ansi 'abstract.I<>Mangled'
{
  .method public hidebysig newslot specialname abstract virtual 
          instance int32  get_P() cil managed
  {
  }

  .property instance int32 P()
  {
    .get instance int32 'abstract.I<>Mangled'::get_P()
  }
} // end of class 'abstract.I<>Mangled'

.class public auto ansi beforefieldinit C
       extends [mscorlib]System.Object
       implements 'abstract.I<>Mangled'
{
  .method private hidebysig newslot specialname virtual final 
          instance int32  'abstract.I<>Mangled.get_P'() cil managed
  {
    .override 'abstract.I<>Mangled'::get_P
    ldc.i4.1
    ret
  }

  .method public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    ldarg.0
    call       instance void [mscorlib]System.Object::.ctor()
    ret
  }

  .property instance int32 'abstract.I<>Mangled.P'()
  {
    .get instance int32 C::'abstract.I<>Mangled.get_P'()
  }

  .property instance int32 P()
  {
    .get instance int32 C::'abstract.I<>Mangled.get_P'()
  }
} // end of class C
";

            ImmutableArray<byte> assemblyBytes;
            ImmutableArray<byte> pdbBytes;
            CSharpTestBase.EmitILToArray(il, appendDefaultHeader: true, includePdb: false, assemblyBytes: out assemblyBytes, pdbBytes: out pdbBytes);
            var assembly = ReflectionUtilities.Load(assemblyBytes);

            var value = CreateDkmClrValue(assembly.GetType("C").Instantiate());

            var root = FormatResult("instance", value);
            Verify(GetChildren(root),
                EvalResult("P", "1", "int", "instance.P", DkmEvaluationResultFlags.ReadOnly | DkmEvaluationResultFlags.CanFavorite, DkmEvaluationResultCategory.Property, DkmEvaluationResultAccessType.Private),
                EvalResult("abstract.I<>Mangled.P", "1", "int", null, DkmEvaluationResultFlags.ReadOnly, DkmEvaluationResultCategory.Property, DkmEvaluationResultAccessType.Private));
        }

        [Fact]
        public void MangledNames_ArrayElement()
        {
            var il = @"
.class public auto ansi beforefieldinit '<>Mangled'
       extends [mscorlib]System.Object
{
  .method public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    ldarg.0
    call       instance void [mscorlib]System.Object::.ctor()
    ret
  }
}

.class public auto ansi beforefieldinit NotMangled
       extends [mscorlib]System.Object
{
  .field public class [mscorlib]System.Collections.Generic.IEnumerable`1<class '<>Mangled'> 'array'
  .method public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    ldarg.0
    ldc.i4.1
    newarr     '<>Mangled'
    stfld      class [mscorlib]System.Collections.Generic.IEnumerable`1<class '<>Mangled'> NotMangled::'array'
    ldarg.0
    call       instance void [mscorlib]System.Object::.ctor()
    ret
  }
}
";

            ImmutableArray<byte> assemblyBytes;
            ImmutableArray<byte> pdbBytes;
            CSharpTestBase.EmitILToArray(il, appendDefaultHeader: true, includePdb: false, assemblyBytes: out assemblyBytes, pdbBytes: out pdbBytes);
            var assembly = ReflectionUtilities.Load(assemblyBytes);

            var value = CreateDkmClrValue(assembly.GetType("NotMangled").Instantiate());

            var root = FormatResult("o", value);
            var children = GetChildren(root);
            Verify(children,
                EvalResult("array", "{<>Mangled[1]}", "System.Collections.Generic.IEnumerable<<>Mangled> {<>Mangled[]}", "o.array", DkmEvaluationResultFlags.Expandable | DkmEvaluationResultFlags.CanFavorite));
            Verify(GetChildren(children.Single()),
                EvalResult("[0]", "null", "<>Mangled", null));
        }

        [Fact]
        public void MangledNames_Namespace()
        {
            var il = @"
.class public auto ansi beforefieldinit '<>Mangled.C' extends [mscorlib]System.Object
{
  .field public static int32 x

  .method public hidebysig specialname rtspecialname instance void  .ctor() cil managed
  {
    ldarg.0
    call       instance void [mscorlib]System.Object::.ctor()
    ret
  }
}
";

            ImmutableArray<byte> assemblyBytes;
            ImmutableArray<byte> pdbBytes;
            CSharpTestBase.EmitILToArray(il, appendDefaultHeader: true, includePdb: false, assemblyBytes: out assemblyBytes, pdbBytes: out pdbBytes);
            var assembly = ReflectionUtilities.Load(assemblyBytes);

            var baseValue = CreateDkmClrValue(assembly.GetType("<>Mangled.C").Instantiate());

            var root = FormatResult("o", baseValue);
            var children = GetChildren(root);
            Verify(children,
                EvalResult("Static members", null, "", null, DkmEvaluationResultFlags.Expandable | DkmEvaluationResultFlags.ReadOnly, DkmEvaluationResultCategory.Class));
            Verify(GetChildren(children.Single()),
                EvalResult("x", "0", "int", null));
        }

        [Fact]
        public void MangledNames_PointerDereference()
        {
            var il = @"
.class public auto ansi beforefieldinit '<>Mangled'
       extends [mscorlib]System.Object
{
  .field private static int32* p

  .method assembly hidebysig specialname rtspecialname 
          instance void  .ctor(int64 arg) cil managed
  {
    IL_0000:  ldarg.0
    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
    IL_0008:  ldarg.1
    IL_0009:  conv.u
    IL_000a:  stsfld     int32* '<>Mangled'::p
    IL_0010:  ret
  }
} // end of class '<>Mangled'
";

            ImmutableArray<byte> assemblyBytes;
            ImmutableArray<byte> pdbBytes;
            CSharpTestBase.EmitILToArray(il, appendDefaultHeader: true, includePdb: false, assemblyBytes: out assemblyBytes, pdbBytes: out pdbBytes);
            var assembly = ReflectionUtilities.Load(assemblyBytes);

            unsafe
            {
                int i = 4;
                long p = (long)&i;
                var type = assembly.GetType("<>Mangled");
                var rootExpr = "m";
                var value = CreateDkmClrValue(type.Instantiate(p));
                var evalResult = FormatResult(rootExpr, value);
                Verify(evalResult,
                    EvalResult(rootExpr, "{<>Mangled}", "<>Mangled", rootExpr, DkmEvaluationResultFlags.Expandable));
                var children = GetChildren(evalResult);
                Verify(children,
                    EvalResult("Static members", null, "", null, DkmEvaluationResultFlags.Expandable | DkmEvaluationResultFlags.ReadOnly, DkmEvaluationResultCategory.Class));
                children = GetChildren(children.Single());
                Verify(children,
                    EvalResult("p", PointerToString(new IntPtr(p)), "int*", null, DkmEvaluationResultFlags.Expandable));
                children = GetChildren(children.Single());
                Verify(children,
                    EvalResult("*p", "4", "int", null));
            }
        }

        [Fact]
        public void MangledNames_DebuggerTypeProxy()
        {
            var il = @"
.class public auto ansi beforefieldinit Type
       extends [mscorlib]System.Object
{
  .custom instance void [mscorlib]System.Diagnostics.DebuggerTypeProxyAttribute::.ctor(class [mscorlib]System.Type)
           = {type('<>Mangled')}
  .field public bool x
  .method public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    ldarg.0
    ldc.i4.0
    stfld      bool Type::x
    ldarg.0
    call       instance void [mscorlib]System.Object::.ctor()
    ret
  } // end of method Type::.ctor

} // end of class Type

.class public auto ansi beforefieldinit '<>Mangled'
       extends [mscorlib]System.Object
{
  .field public bool y
  .method public hidebysig specialname rtspecialname 
          instance void  .ctor(class Type s) cil managed
  {
    ldarg.0
    ldc.i4.1
    stfld      bool '<>Mangled'::y
    ldarg.0
    call       instance void [mscorlib]System.Object::.ctor()
    ret
  } // end of method '<>Mangled'::.ctor

} // end of class '<>Mangled'
";

            ImmutableArray<byte> assemblyBytes;
            ImmutableArray<byte> pdbBytes;
            CSharpTestBase.EmitILToArray(il, appendDefaultHeader: true, includePdb: false, assemblyBytes: out assemblyBytes, pdbBytes: out pdbBytes);
            var assembly = ReflectionUtilities.Load(assemblyBytes);

            var value = CreateDkmClrValue(assembly.GetType("Type").Instantiate());

            var root = FormatResult("o", value);
            var children = GetChildren(root);
            Verify(children,
                EvalResult("y", "true", "bool", null, DkmEvaluationResultFlags.Boolean | DkmEvaluationResultFlags.BooleanTrue),
                EvalResult("Raw View", null, "", "o, raw", DkmEvaluationResultFlags.Expandable | DkmEvaluationResultFlags.ReadOnly, DkmEvaluationResultCategory.Data));

            var grandChildren = GetChildren(children.Last());
            Verify(grandChildren,
                EvalResult("x", "false", "bool", "o.x", DkmEvaluationResultFlags.Boolean));
        }

        [Fact]
        public void GenericTypeWithoutBacktick()
        {
            var il = @"
.class public auto ansi beforefieldinit C<T> extends [mscorlib]System.Object
{
  .field public static int32 'x'

  .method public hidebysig specialname rtspecialname instance void  .ctor() cil managed
  {
    ldarg.0
    call       instance void [mscorlib]System.Object::.ctor()
    ret
  }
}
";

            ImmutableArray<byte> assemblyBytes;
            ImmutableArray<byte> pdbBytes;
            CSharpTestBase.EmitILToArray(il, appendDefaultHeader: true, includePdb: false, assemblyBytes: out assemblyBytes, pdbBytes: out pdbBytes);
            var assembly = ReflectionUtilities.Load(assemblyBytes);

            var value = CreateDkmClrValue(assembly.GetType("C").MakeGenericType(typeof(int)).Instantiate());

            var root = FormatResult("o", value);
            var children = GetChildren(root);
            Verify(children,
                EvalResult("Static members", null, "", "C<int>", DkmEvaluationResultFlags.Expandable | DkmEvaluationResultFlags.ReadOnly, DkmEvaluationResultCategory.Class));
            Verify(GetChildren(children.Single()),
                EvalResult("x", "0", "int", "C<int>.x"));
        }

        [Fact]
        public void BackTick_NonGenericType()
        {
            var il = @"
.class public auto ansi beforefieldinit 'C`1' extends [mscorlib]System.Object
{
  .field public static int32 'x'

  .method public hidebysig specialname rtspecialname instance void  .ctor() cil managed
  {
    ldarg.0
    call       instance void [mscorlib]System.Object::.ctor()
    ret
  }
}
";

            ImmutableArray<byte> assemblyBytes;
            ImmutableArray<byte> pdbBytes;
            CSharpTestBase.EmitILToArray(il, appendDefaultHeader: true, includePdb: false, assemblyBytes: out assemblyBytes, pdbBytes: out pdbBytes);
            var assembly = ReflectionUtilities.Load(assemblyBytes);

            var value = CreateDkmClrValue(assembly.GetType("C`1").Instantiate());

            var root = FormatResult("o", value);
            var children = GetChildren(root);
            Verify(children,
                EvalResult("Static members", null, "", null, DkmEvaluationResultFlags.Expandable | DkmEvaluationResultFlags.ReadOnly, DkmEvaluationResultCategory.Class));
            Verify(GetChildren(children.Single()),
                EvalResult("x", "0", "int", null));
        }

        [Fact]
        public void BackTick_GenericType()
        {
            var il = @"
.class public auto ansi beforefieldinit 'C`1'<T> extends [mscorlib]System.Object
{
  .field public static int32 'x'

  .method public hidebysig specialname rtspecialname instance void  .ctor() cil managed
  {
    ldarg.0
    call       instance void [mscorlib]System.Object::.ctor()
    ret
  }
}
";

            ImmutableArray<byte> assemblyBytes;
            ImmutableArray<byte> pdbBytes;
            CSharpTestBase.EmitILToArray(il, appendDefaultHeader: true, includePdb: false, assemblyBytes: out assemblyBytes, pdbBytes: out pdbBytes);
            var assembly = ReflectionUtilities.Load(assemblyBytes);

            var value = CreateDkmClrValue(assembly.GetType("C`1").MakeGenericType(typeof(int)).Instantiate());

            var root = FormatResult("o", value);
            var children = GetChildren(root);
            Verify(children,
                EvalResult("Static members", null, "", "C<int>", DkmEvaluationResultFlags.Expandable | DkmEvaluationResultFlags.ReadOnly, DkmEvaluationResultCategory.Class));
            Verify(GetChildren(children.Single()),
                EvalResult("x", "0", "int", "C<int>.x"));
        }

        [Fact]
        public void BackTick_Member()
        {
            // IL doesn't support using generic methods as property accessors so
            // there's no way to test a "legitimate" backtick in a member name.
            var il = @"
.class public auto ansi beforefieldinit C extends [mscorlib]System.Object
{
  .field public static int32 'x`1'

  .method public hidebysig specialname rtspecialname instance void  .ctor() cil managed
  {
    ldarg.0
    call       instance void [mscorlib]System.Object::.ctor()
    ret
  }
}
";

            ImmutableArray<byte> assemblyBytes;
            ImmutableArray<byte> pdbBytes;
            CSharpTestBase.EmitILToArray(il, appendDefaultHeader: true, includePdb: false, assemblyBytes: out assemblyBytes, pdbBytes: out pdbBytes);
            var assembly = ReflectionUtilities.Load(assemblyBytes);

            var value = CreateDkmClrValue(assembly.GetType("C").Instantiate());

            var root = FormatResult("o", value);
            var children = GetChildren(root);
            Verify(children,
                EvalResult("Static members", null, "", "C", DkmEvaluationResultFlags.Expandable | DkmEvaluationResultFlags.ReadOnly, DkmEvaluationResultCategory.Class));
            Verify(GetChildren(children.Single()),
                EvalResult("x`1", "0", "int", fullName: null));
        }

        [Fact]
        public void BackTick_FirstCharacter()
        {
            var il = @"
.class public auto ansi beforefieldinit '`1'<T> extends [mscorlib]System.Object
{
  .field public static int32 'x'

  .method public hidebysig specialname rtspecialname instance void  .ctor() cil managed
  {
    ldarg.0
    call       instance void [mscorlib]System.Object::.ctor()
    ret
  }
}
";

            ImmutableArray<byte> assemblyBytes;
            ImmutableArray<byte> pdbBytes;
            CSharpTestBase.EmitILToArray(il, appendDefaultHeader: true, includePdb: false, assemblyBytes: out assemblyBytes, pdbBytes: out pdbBytes);
            var assembly = ReflectionUtilities.Load(assemblyBytes);

            var value = CreateDkmClrValue(assembly.GetType("`1").MakeGenericType(typeof(int)).Instantiate());

            var root = FormatResult("o", value);
            var children = GetChildren(root);
            Verify(children,
                EvalResult("Static members", null, "", null, DkmEvaluationResultFlags.Expandable | DkmEvaluationResultFlags.ReadOnly, DkmEvaluationResultCategory.Class));
            Verify(GetChildren(children.Single()),
                EvalResult("x", "0", "int", fullName: null));
        }

        [Fact]
        public void MangledName_SimplifySynthesizedLocalName()
        {
            IDkmClrFullNameProvider2 fullNameProvider = new CSharpFormatter();
            var inspectionContext = CreateDkmInspectionContext();
            // The synthesized locals name should just become an empty string since it's compiler generated.
            Assert.Equal(string.Empty, fullNameProvider.GetClrNameForLocalVariable(inspectionContext, null, default, default, new DkmClrLocalVariable("CS$<>8__locals0")));
        }

        [Fact]
        public void MangledName_SimplifyThisProxyField()
        {
            var il = @"
.class public C
{
  .field public object '<>4__this'
}
";
            ImmutableArray<byte> assemblyBytes;
            ImmutableArray<byte> pdbBytes;
            CSharpTestBase.EmitILToArray(il, appendDefaultHeader: true, includePdb: false, assemblyBytes: out assemblyBytes, pdbBytes: out pdbBytes);
            var assembly = ReflectionUtilities.Load(assemblyBytes);

            var fieldToken = assembly.GetType("C").GetFields().First().MetadataToken;

            IDkmClrFullNameProvider2 fullNameProvider = new CSharpFormatter();
            var inspectionContext = CreateDkmInspectionContext();
            // The stashed <>4__this should just become an empty string since it's compiler generated.
            Assert.Equal(string.Empty, fullNameProvider.GetClrNameForField(inspectionContext, new DkmClrRuntimeInstance(assembly).Modules[0], fieldToken));
        }

        [Fact]
        public void MangledName_SimplifyHoistedLocal()
        {
            var il = @"
.class public C
{
  .field public object '<myClass>5__1'
}
";
            ImmutableArray<byte> assemblyBytes;
            ImmutableArray<byte> pdbBytes;
            CSharpTestBase.EmitILToArray(il, appendDefaultHeader: true, includePdb: false, assemblyBytes: out assemblyBytes, pdbBytes: out pdbBytes);
            var assembly = ReflectionUtilities.Load(assemblyBytes);

            var fieldToken = assembly.GetType("C").GetFields().First().MetadataToken;

            IDkmClrFullNameProvider2 fullNameProvider = new CSharpFormatter();
            var inspectionContext = CreateDkmInspectionContext();
            Assert.Equal("myClass", fullNameProvider.GetClrNameForField(inspectionContext, new DkmClrRuntimeInstance(assembly).Modules[0], fieldToken));
        }

        [Fact]
        public void MangledName_SimplifyBackingField_01()
        {
            var il = @"
.class public C
{
  .field public object '<StringProperty>k__BackingField'
}
";
            ImmutableArray<byte> assemblyBytes;
            ImmutableArray<byte> pdbBytes;
            CSharpTestBase.EmitILToArray(il, appendDefaultHeader: true, includePdb: false, assemblyBytes: out assemblyBytes, pdbBytes: out pdbBytes);
            var assembly = ReflectionUtilities.Load(assemblyBytes);

            var fieldToken = assembly.GetType("C").GetFields().First().MetadataToken;

            IDkmClrFullNameProvider2 fullNameProvider = new CSharpFormatter();
            var inspectionContext = CreateDkmInspectionContext();
            Assert.Equal("StringProperty", fullNameProvider.GetClrNameForField(inspectionContext, new DkmClrRuntimeInstance(assembly).Modules[0], fieldToken));
        }

        [Fact]
        public void MangledName_SimplifyBackingField_02()
        {
            var il = @"
.class public C
{
  .field public object '<StringParameter>P'
}
";
            ImmutableArray<byte> assemblyBytes;
            ImmutableArray<byte> pdbBytes;
            CSharpTestBase.EmitILToArray(il, appendDefaultHeader: true, includePdb: false, assemblyBytes: out assemblyBytes, pdbBytes: out pdbBytes);
            var assembly = ReflectionUtilities.Load(assemblyBytes);

            var fieldToken = assembly.GetType("C").GetFields().First().MetadataToken;

            IDkmClrFullNameProvider2 fullNameProvider = new CSharpFormatter();
            var inspectionContext = CreateDkmInspectionContext();
            Assert.Equal("<StringParameter>P", fullNameProvider.GetClrNameForField(inspectionContext, new DkmClrRuntimeInstance(assembly).Modules[0], fieldToken));
        }
    }
}
