diff --git a/src/ExpressionEvaluator/CSharp/Test/ResultProvider/Microsoft.CodeAnalysis.CSharp.ResultProvider.UnitTests.csproj b/src/ExpressionEvaluator/CSharp/Test/ResultProvider/Microsoft.CodeAnalysis.CSharp.ResultProvider.UnitTests.csproj
index 444ab1bd8ef7a..fe56796c9181a 100644
--- a/src/ExpressionEvaluator/CSharp/Test/ResultProvider/Microsoft.CodeAnalysis.CSharp.ResultProvider.UnitTests.csproj
+++ b/src/ExpressionEvaluator/CSharp/Test/ResultProvider/Microsoft.CodeAnalysis.CSharp.ResultProvider.UnitTests.csproj
@@ -5,7 +5,7 @@
Library
Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator
Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator.ResultProvider.UnitTests
- net472
+ net472;$(NetRoslyn)
true
@@ -17,10 +17,25 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ExpressionEvaluator/CSharp/Test/ResultProvider/NetCoreTests/InlineArrayExpansionTests.cs b/src/ExpressionEvaluator/CSharp/Test/ResultProvider/NetCoreTests/InlineArrayExpansionTests.cs
new file mode 100644
index 0000000000000..35010b802ba34
--- /dev/null
+++ b/src/ExpressionEvaluator/CSharp/Test/ResultProvider/NetCoreTests/InlineArrayExpansionTests.cs
@@ -0,0 +1,35 @@
+// 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 Microsoft.CodeAnalysis.ExpressionEvaluator;
+using Microsoft.VisualStudio.Debugger.Evaluation;
+using Xunit;
+
+namespace Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator.UnitTests.NetCoreTests;
+
+public class InlineArrayExpansionTests : CSharpResultProviderTestBase
+{
+ [Fact]
+ public void InlineArrayExpansion()
+ {
+ var hostObject = new SampleInlineArray();
+ for (int i = 0; i < SampleInlineArray.Length; i++)
+ {
+ hostObject[i] = i;
+ }
+
+ var value = CreateDkmClrValue(hostObject, typeof(SampleInlineArray), evalFlags: DkmEvaluationResultFlags.None);
+
+ const string rootExpr = "new SampleInlineArray()";
+ var evalResult = (DkmSuccessEvaluationResult)FormatResult(rootExpr, value);
+ Verify(evalResult,
+ EvalResult(rootExpr, "0,1,2,3", "Microsoft.CodeAnalysis.ExpressionEvaluator.SampleInlineArray", rootExpr, DkmEvaluationResultFlags.Expandable));
+
+ Verify(GetChildren(evalResult),
+ EvalResult("[0]", "0", "int", "(new SampleInlineArray())[0]", DkmEvaluationResultFlags.None),
+ EvalResult("[1]", "1", "int", "(new SampleInlineArray())[1]", DkmEvaluationResultFlags.None),
+ EvalResult("[2]", "2", "int", "(new SampleInlineArray())[2]", DkmEvaluationResultFlags.None),
+ EvalResult("[3]", "3", "int", "(new SampleInlineArray())[3]", DkmEvaluationResultFlags.None));
+ }
+}
diff --git a/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/ExpressionEvaluatorFatalError.cs b/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/ExpressionEvaluatorFatalError.cs
index ede4341c1ea7e..0ad1905244eff 100644
--- a/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/ExpressionEvaluatorFatalError.cs
+++ b/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/ExpressionEvaluatorFatalError.cs
@@ -27,7 +27,8 @@ internal static class RegistryHelpers
var hKeyCurrentUserField = registryType.GetTypeInfo().GetDeclaredField("CurrentUser");
if (hKeyCurrentUserField != null && hKeyCurrentUserField.IsStatic)
{
- using var currentUserKey = (IDisposable)hKeyCurrentUserField.GetValue(null);
+ using var currentUserKey = (IDisposable?)hKeyCurrentUserField.GetValue(null);
+ RoslynDebug.AssertNotNull(currentUserKey);
var openSubKeyMethod = currentUserKey.GetType().GetTypeInfo().GetDeclaredMethod("OpenSubKey", [typeof(string), typeof(bool)]);
using var eeKey = (IDisposable?)openSubKeyMethod?.Invoke(currentUserKey, new object[] { RegistryKey, /*writable*/ false });
diff --git a/src/ExpressionEvaluator/Core/Source/ResultProvider/Helpers/InlineArrayHelpers.cs b/src/ExpressionEvaluator/Core/Source/ResultProvider/Helpers/InlineArrayHelpers.cs
new file mode 100644
index 0000000000000..01f6a5d16d661
--- /dev/null
+++ b/src/ExpressionEvaluator/Core/Source/ResultProvider/Helpers/InlineArrayHelpers.cs
@@ -0,0 +1,61 @@
+// 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.Diagnostics.CodeAnalysis;
+using BindingFlags = Microsoft.VisualStudio.Debugger.Metadata.BindingFlags;
+using CustomAttributeData = Microsoft.VisualStudio.Debugger.Metadata.CustomAttributeData;
+using FieldInfo = Microsoft.VisualStudio.Debugger.Metadata.FieldInfo;
+using Type = Microsoft.VisualStudio.Debugger.Metadata.Type;
+
+namespace Microsoft.CodeAnalysis.ExpressionEvaluator;
+
+internal static class InlineArrayHelpers
+{
+ private const string InlineArrayAttributeName = "System.Runtime.CompilerServices.InlineArrayAttribute";
+
+ public static bool TryGetInlineArrayInfo(Type t, out int arrayLength, [NotNullWhen(true)] out Type? tElementType)
+ {
+ arrayLength = -1;
+ tElementType = null;
+
+ if (!t.IsValueType)
+ {
+ return false;
+ }
+
+ IList customAttributes = t.GetCustomAttributesData();
+ foreach (var attribute in customAttributes)
+ {
+ if (InlineArrayAttributeName.Equals(attribute.Constructor?.DeclaringType?.FullName))
+ {
+ var ctorParams = attribute.Constructor.GetParameters();
+ if (ctorParams.Length == 1 && ctorParams[0].ParameterType.IsInt32() &&
+ attribute.ConstructorArguments.Count == 1 && attribute.ConstructorArguments[0].Value is int length)
+ {
+ arrayLength = length;
+ }
+ }
+ }
+
+ // Inline arrays must have length > 0
+ if (arrayLength <= 0)
+ {
+ return false;
+ }
+
+ FieldInfo[] fields = t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
+ if (fields.Length == 1)
+ {
+ tElementType = fields[0].FieldType;
+ }
+ else
+ {
+ // Inline arrays must have exactly one field
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/src/ExpressionEvaluator/Core/Source/ResultProvider/Helpers/TypeHelpers.cs b/src/ExpressionEvaluator/Core/Source/ResultProvider/Helpers/TypeHelpers.cs
index 91bf946abbe7c..35fa353196e0e 100644
--- a/src/ExpressionEvaluator/Core/Source/ResultProvider/Helpers/TypeHelpers.cs
+++ b/src/ExpressionEvaluator/Core/Source/ResultProvider/Helpers/TypeHelpers.cs
@@ -278,6 +278,11 @@ private static MethodInfo GetNonIndexerGetMethod(PropertyInfo property)
: null;
}
+ internal static bool IsInt32(this Type type)
+ {
+ return Type.GetTypeCode(type) == TypeCode.Int32;
+ }
+
internal static bool IsBoolean(this Type type)
{
return Type.GetTypeCode(type) == TypeCode.Boolean;
diff --git a/src/ExpressionEvaluator/Core/Source/ResultProvider/ResultProvider.cs b/src/ExpressionEvaluator/Core/Source/ResultProvider/ResultProvider.cs
index a3bd70266c3c4..770923d603f10 100644
--- a/src/ExpressionEvaluator/Core/Source/ResultProvider/ResultProvider.cs
+++ b/src/ExpressionEvaluator/Core/Source/ResultProvider/ResultProvider.cs
@@ -1012,6 +1012,15 @@ internal Expansion GetTypeExpansion(
return TupleExpansion.CreateExpansion(inspectionContext, declaredTypeAndInfo, value, cardinality);
}
+ if (InlineArrayHelpers.TryGetInlineArrayInfo(runtimeType, out int inlineArrayLength, out Type inlineArrayElementType))
+ {
+ // Inline arrays are always 1D, zero-based arrays.
+ return ArrayExpansion.CreateExpansion(
+ elementTypeAndInfo: new TypeAndCustomInfo(DkmClrType.Create(declaredTypeAndInfo.ClrType.AppDomain, inlineArrayElementType), null),
+ sizes: new ReadOnlyCollection([inlineArrayLength]),
+ lowerBounds: new ReadOnlyCollection([0]));
+ }
+
return MemberExpansion.CreateExpansion(inspectionContext, declaredTypeAndInfo, value, flags, TypeHelpers.IsVisibleMember, this, isProxyType: false, supportsFavorites);
}
diff --git a/src/ExpressionEvaluator/Core/Source/ResultProvider/ResultProvider.projitems b/src/ExpressionEvaluator/Core/Source/ResultProvider/ResultProvider.projitems
index e612fce34c869..7a25078611b18 100644
--- a/src/ExpressionEvaluator/Core/Source/ResultProvider/ResultProvider.projitems
+++ b/src/ExpressionEvaluator/Core/Source/ResultProvider/ResultProvider.projitems
@@ -16,6 +16,7 @@
+
diff --git a/src/ExpressionEvaluator/Core/Test/ResultProvider/Debugger/Engine/DkmClrValue.cs b/src/ExpressionEvaluator/Core/Test/ResultProvider/Debugger/Engine/DkmClrValue.cs
index c76545d9d58c9..4f4cb8e4a8d5c 100644
--- a/src/ExpressionEvaluator/Core/Test/ResultProvider/Debugger/Engine/DkmClrValue.cs
+++ b/src/ExpressionEvaluator/Core/Test/ResultProvider/Debugger/Engine/DkmClrValue.cs
@@ -565,9 +565,45 @@ public DkmClrValue GetArrayElement(int[] indices, DkmInspectionContext inspectio
throw new ArgumentNullException(nameof(inspectionContext));
}
- var array = (System.Array)RawValue;
- var element = array.GetValue(indices);
- var type = DkmClrType.Create(this.Type.AppDomain, (TypeImpl)((element == null) ? array.GetType().GetElementType() : element.GetType()));
+ object element;
+ System.Type elementType;
+ if (RawValue is Array array)
+ {
+ element = array.GetValue(indices);
+ elementType = (element == null) ? array.GetType().GetElementType() : element.GetType();
+ }
+ else
+ {
+#if NET8_0_OR_GREATER
+ // Might be an inline array struct
+ if (indices.Length == 1 && InlineArrayHelpers.TryGetInlineArrayInfo(Type.GetLmrType(), out _, out _))
+ {
+ // Since reflection is inadequate to dynamically access inline array elements,
+ // we have to assume it's the special SampleInlineArray type we define for testing and
+ // cast appropriately.
+ element = RawValue switch
+ {
+ SampleInlineArray intInlineArray => intInlineArray[indices[0]],
+ // Add more cases here for other types as needed for testing
+ _ => throw new InvalidOperationException($"Missing cast case for SampleInlineArray"),
+ };
+
+ var fields = RawValue.GetType().GetFields(System.Reflection.BindingFlags.Public |
+ System.Reflection.BindingFlags.NonPublic |
+ System.Reflection.BindingFlags.Instance |
+ System.Reflection.BindingFlags.DeclaredOnly);
+ elementType = fields[0].FieldType;
+ }
+ else
+ {
+ throw new InvalidOperationException("Not an array");
+ }
+#else
+ throw new InvalidOperationException("Not an array");
+#endif
+ }
+
+ var type = DkmClrType.Create(this.Type.AppDomain, (TypeImpl)(elementType));
return new DkmClrValue(
element,
element,
diff --git a/src/ExpressionEvaluator/Core/Test/ResultProvider/Debugger/MemberInfo/ConstructorInfoImpl.cs b/src/ExpressionEvaluator/Core/Test/ResultProvider/Debugger/MemberInfo/ConstructorInfoImpl.cs
index e3060c4c7c83b..dd72573c11413 100644
--- a/src/ExpressionEvaluator/Core/Test/ResultProvider/Debugger/MemberInfo/ConstructorInfoImpl.cs
+++ b/src/ExpressionEvaluator/Core/Test/ResultProvider/Debugger/MemberInfo/ConstructorInfoImpl.cs
@@ -8,6 +8,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
+using System.Linq;
using Microsoft.VisualStudio.Debugger.Metadata;
using Type = Microsoft.VisualStudio.Debugger.Metadata.Type;
@@ -135,7 +136,7 @@ public override System.Reflection.MethodImplAttributes GetMethodImplementationFl
public override Microsoft.VisualStudio.Debugger.Metadata.ParameterInfo[] GetParameters()
{
- throw new NotImplementedException();
+ return [.. Constructor.GetParameters().Select(p => new ParameterInfoImpl(p))];
}
public override object Invoke(BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture)
diff --git a/src/ExpressionEvaluator/Core/Test/ResultProvider/Debugger/MemberInfo/ParameterInfoImpl.cs b/src/ExpressionEvaluator/Core/Test/ResultProvider/Debugger/MemberInfo/ParameterInfoImpl.cs
index 193664f1eed01..5a250f1684d22 100644
--- a/src/ExpressionEvaluator/Core/Test/ResultProvider/Debugger/MemberInfo/ParameterInfoImpl.cs
+++ b/src/ExpressionEvaluator/Core/Test/ResultProvider/Debugger/MemberInfo/ParameterInfoImpl.cs
@@ -37,20 +37,11 @@ public override MemberInfo Member
get { throw new NotImplementedException(); }
}
- public override string Name
- {
- get { throw new NotImplementedException(); }
- }
+ public override string Name => Parameter.Name;
- public override Type ParameterType
- {
- get { throw new NotImplementedException(); }
- }
+ public override Type ParameterType => new TypeImpl(Parameter.ParameterType);
- public override int Position
- {
- get { throw new NotImplementedException(); }
- }
+ public override int Position => Parameter.Position;
public override object RawDefaultValue
{
diff --git a/src/ExpressionEvaluator/Core/Test/ResultProvider/Microsoft.CodeAnalysis.ResultProvider.Utilities.csproj b/src/ExpressionEvaluator/Core/Test/ResultProvider/Microsoft.CodeAnalysis.ResultProvider.Utilities.csproj
index cdc5d592e5ce2..b27b900a4fb13 100644
--- a/src/ExpressionEvaluator/Core/Test/ResultProvider/Microsoft.CodeAnalysis.ResultProvider.Utilities.csproj
+++ b/src/ExpressionEvaluator/Core/Test/ResultProvider/Microsoft.CodeAnalysis.ResultProvider.Utilities.csproj
@@ -6,7 +6,7 @@
Microsoft.CodeAnalysis.ExpressionEvaluator
Microsoft.CodeAnalysis.ExpressionEvaluator.ResultProvider.Utilities
true
- net472
+ net472;$(NetRoslyn)
false
true
@@ -23,14 +23,14 @@
$(NoWarn);CA1825
-
+
+
-
diff --git a/src/ExpressionEvaluator/Core/Test/ResultProvider/SampleInlineArray.cs b/src/ExpressionEvaluator/Core/Test/ResultProvider/SampleInlineArray.cs
new file mode 100644
index 0000000000000..e2908938eed70
--- /dev/null
+++ b/src/ExpressionEvaluator/Core/Test/ResultProvider/SampleInlineArray.cs
@@ -0,0 +1,37 @@
+// 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.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Microsoft.VisualStudio.Debugger.Evaluation;
+using Microsoft.VisualStudio.Debugger.Evaluation.ClrCompilation;
+
+namespace Microsoft.CodeAnalysis.ExpressionEvaluator;
+
+#if NET8_0_OR_GREATER
+
+///
+/// Reflection APIs are inadequate to introspect inline arrays as they box the value type, making
+/// it impossible to take a ref to the first element field and read elements out of it.
+/// This definition is intended to be used when testing inline arrays.
+///
+///
+/// To support testing new types,
+/// add an appropriate cast in
+///
+[InlineArray(Length)]
+[DebuggerDisplay("{ToString(),nq}")]
+internal struct SampleInlineArray
+{
+ public const int Length = 4;
+ public T _elem0;
+
+ public override string ToString()
+ {
+ return string.Join(",", MemoryMarshal.CreateSpan(ref _elem0, Length).ToArray());
+ }
+}
+
+#endif