Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@
<Compile Include="TestCases\ILPretty\MonoFixed.cs" />
<Compile Include="TestCases\Pretty\Comparisons.cs" />
<Compile Include="TestCases\Pretty\GloballyQualifiedTypeInStringInterpolation.cs" />
<Compile Include="TestCases\Pretty\InlineArrayTests.cs" />
<Compile Include="TestCases\Pretty\Issue3406.cs" />
<Compile Include="TestCases\Pretty\Issue3439.cs" />
<Compile Include="TestCases\Pretty\Issue3442.cs" />
Expand Down
6 changes: 6 additions & 0 deletions ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,12 @@ public async Task PointerArithmetic([ValueSource(nameof(defaultOptions))] Compil
await RunForLibrary(cscOptions: cscOptions);
}

[Test]
public async Task InlineArrayTests([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions)
{
await RunForLibrary(cscOptions: cscOptions);
}

async Task RunForLibrary([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, Action<DecompilerSettings> configureDecompiler = null)
{
await Run(testName, asmOptions | AssemblerOptions.Library, cscOptions | CompilerOptions.Library, configureDecompiler);
Expand Down
32 changes: 32 additions & 0 deletions ICSharpCode.Decompiler.Tests/TestCases/Correctness/Conversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
// #include "../../../ICSharpCode.Decompiler/Util/CSharpPrimitiveCast.cs"

using System;
using System.Runtime.CompilerServices;

using ICSharpCode.Decompiler.Util;

Expand Down Expand Up @@ -111,6 +112,9 @@ static void Main(string[] args)

Console.WriteLine(ReadZeroTerminatedString("Hello World!".Length));
C1.Test();
#if ROSLYN2 && !NET40
C3.Run();
#endif
}

static void RunTest(bool checkForOverflow)
Expand Down Expand Up @@ -199,4 +203,32 @@ public static implicit operator C1(C2 c)
return new C1();
}
}

#if ROSLYN2 && !NET40
class C3
{
[InlineArray(4)] struct MyArray { private int elem; }

static void Foo(object o)
{
Console.WriteLine("Foo(object) called");
}

static void Foo(ReadOnlySpan<int> o)
{
Console.WriteLine("Foo(ReadOnlySpan<int>) called");
}

static void Test(MyArray arr)
{
Foo((object)arr);
}

public static void Run()
{
Console.WriteLine("C3.Run() called");
Test(default);
}
}
#endif
}
155 changes: 155 additions & 0 deletions ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineArrayTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
using System;
using System.Runtime.CompilerServices;

namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
public class InlineArrayTests
{
[InlineArray(16)]
public struct Byte16
{
private byte elem;
}

[InlineArray(16)]
public struct Generic16<T>
{
private T elem;
}

public byte Byte0()
{
return GetByte16()[0];
}

public byte GenericByte0()
{
return GetGeneric<byte>()[0];
}

public byte Byte5()
{
return GetByte16()[5];
}

public byte GenericByte5()
{
return GetGeneric<byte>()[5];
}

public byte ByteN()
{
return GetByte16()[GetIndex()];
}

public byte GenericByteN()
{
return GetGeneric<byte>()[GetIndex()];
}

public byte Byte0(Byte16 array, byte value)
{
return array[0] = value;
}

public byte GenericByte0(Generic16<byte> array, byte value)
{
return array[0] = value;
}

public byte Byte5(Byte16 array, byte value)
{
return array[5] = value;
}

public byte GenericByte5(Generic16<byte> array, byte value)
{
return array[5] = value;
}

public byte ByteN(Byte16 array, byte value)
{
return array[GetIndex()] = value;
}

public byte GenericByteN(Generic16<byte> array, byte value)
{
return array[GetIndex()] = value;
}

public void Slice(Byte16 array)
{
Receiver(array[..8]);
Receiver((ReadOnlySpan<byte>)array[..8]);
ReceiverSpan(array[..8]);
ReceiverReadOnlySpan(array[..8]);
}

// TODO
//public void Slice(Byte16 array, int end)
//{
// Receiver(array[..end]);
// Receiver((ReadOnlySpan<byte>)array[..end]);
// ReceiverSpan(array[..end]);
// ReceiverReadOnlySpan(array[..end]);
//}

public byte VariableSplitting(Byte16 array, byte value)
{
return array[GetIndex()] = (array[GetIndex() + 1] = value);
}

public void OverloadResolution()
{
Receiver(GetByte16());
Receiver((object)GetByte16());
Byte16 buffer = GetByte16();
Receiver((Span<byte>)buffer);
Byte16 buffer2 = GetByte16();
Receiver((ReadOnlySpan<byte>)buffer2);
Byte16 buffer3 = GetByte16();
ReceiverSpan(buffer3);
Byte16 buffer4 = GetByte16();
ReceiverReadOnlySpan(buffer4);
}

public Byte16 GetByte16()
{
return default(Byte16);
}

public Generic16<T> GetGeneric<T>()
{
return default(Generic16<T>);
}

public int GetIndex()
{
return 0;
}

public void Receiver(Span<byte> span)
{
}

public void Receiver(ReadOnlySpan<byte> span)
{
}

public void Receiver(Byte16 span)
{
}

public void Receiver(object span)
{
}

public void ReceiverSpan(Span<byte> span)
{
}

public void ReceiverReadOnlySpan(ReadOnlySpan<byte> span)
{
}
}
}
36 changes: 36 additions & 0 deletions ICSharpCode.Decompiler/CSharp/CallBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,42 @@ public ExpressionWithResolveResult Build(OpCode callOpCode, IMethod method,
return HandleImplicitConversion(method, argumentList.Arguments[0]);
}

if (settings.InlineArrays
&& method is { DeclaringType.FullName: "<PrivateImplementationDetails>", Name: "InlineArrayAsSpan" or "InlineArrayAsReadOnlySpan" }
&& argumentList.Length == 2)
{
argumentList.CheckNoNamedOrOptionalArguments();
var arrayType = method.TypeArguments[0];
var arrayLength = arrayType.GetInlineArrayLength();
var arrayElementType = arrayType.GetInlineArrayElementType();
var argument = argumentList.Arguments[0];
var spanLengthExpr = argumentList.Arguments[1];
var targetType = method.ReturnType;
var spanType = typeSystem.FindType(KnownTypeCode.SpanOfT);
if (argument.Expression is DirectionExpression { FieldDirection: FieldDirection.In or FieldDirection.Ref, Expression: var lvalueExpr })
{
// `(TargetType)(in arg)` is invalid syntax.
// Also, `f(in arg)` is invalid when there's an implicit conversion involved.
argument = argument.UnwrapChild(lvalueExpr);
}
if (spanLengthExpr.ResolveResult.ConstantValue is int spanLength && spanLength <= arrayLength)
{
if (spanLength < arrayLength)
{
argument = new IndexerExpression(argument.Expression, new BinaryOperatorExpression {
Operator = BinaryOperatorType.Range,
Right = spanLengthExpr.Expression
}).WithRR(new ResolveResult(new ParameterizedType(spanType, arrayElementType))).WithoutILInstruction();
if (targetType.IsKnownType(KnownTypeCode.SpanOfT))
{
return argument;
}
}
return new CastExpression(expressionBuilder.ConvertType(targetType), argument.Expression)
.WithRR(new ConversionResolveResult(targetType, argument.ResolveResult, Conversion.InlineArrayConversion));
}
}

if (settings.LiftNullables && method.Name == "GetValueOrDefault"
&& method.DeclaringType.IsKnownType(KnownTypeCode.NullableOfT)
&& method.DeclaringType.TypeArguments[0].IsKnownType(KnownTypeCode.Boolean)
Expand Down
17 changes: 17 additions & 0 deletions ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3130,6 +3130,23 @@ protected internal override TranslatedExpression VisitLdElema(LdElema inst, Tran
.WithoutILInstruction().WithRR(new ByReferenceResolveResult(expr.ResolveResult, ReferenceKind.Ref));
}

protected internal override TranslatedExpression VisitLdElemaInlineArray(LdElemaInlineArray inst, TranslationContext context)
{
TranslatedExpression arrayExpr = TranslateTarget(
inst.Array,
nonVirtualInvocation: true,
memberStatic: false,
memberDeclaringType: inst.Type
);
var inlineArrayElementType = inst.Type.GetInlineArrayElementType();
IndexerExpression indexerExpr = new IndexerExpression(
arrayExpr, inst.Indices.Select(i => TranslateArrayIndex(i).Expression)
);
TranslatedExpression expr = indexerExpr.WithILInstruction(inst).WithRR(new ResolveResult(inlineArrayElementType));
return new DirectionExpression(FieldDirection.Ref, expr)
.WithoutILInstruction().WithRR(new ByReferenceResolveResult(expr.ResolveResult, ReferenceKind.Ref));
}

TranslatedExpression TranslateArrayIndex(ILInstruction i)
{
var input = Translate(i);
Expand Down
12 changes: 10 additions & 2 deletions ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ private Conversion ImplicitConversion(ResolveResult resolveResult, IType toType,
if (c != Conversion.None)
return c;
}
if (resolveResult is InterpolatedStringResolveResult isrr)
if (resolveResult is InterpolatedStringResolveResult)
{
if (toType.IsKnownType(KnownTypeCode.IFormattable) || toType.IsKnownType(KnownTypeCode.FormattableString))
return Conversion.ImplicitInterpolatedStringConversion;
Expand Down Expand Up @@ -230,12 +230,20 @@ Conversion StandardImplicitConversion(IType fromType, IType toType, bool allowTu
if (c != Conversion.None)
return c;
}
if ((toType.IsKnownType(KnownTypeCode.SpanOfT) || toType.IsKnownType(KnownTypeCode.ReadOnlySpanOfT))
&& fromType.IsInlineArrayType())
{
var elementType = fromType.GetInlineArrayElementType();
var spanElementType = toType.TypeArguments[0];
if (IdentityConversion(elementType, spanElementType))
return Conversion.InlineArrayConversion;
}
return Conversion.None;
}

/// <summary>
/// Gets whether the type 'fromType' is convertible to 'toType'
/// using one of the conversions allowed when satisying constraints (§4.4.4)
/// using one of the conversions allowed when satisfying constraints (§4.4.4)
/// </summary>
public bool IsConstraintConvertible(IType fromType, IType toType)
{
Expand Down
21 changes: 20 additions & 1 deletion ICSharpCode.Decompiler/DecompilerSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,12 +164,13 @@ public void SetLanguageVersion(CSharp.LanguageVersion languageVersion)
{
refReadOnlyParameters = false;
usePrimaryConstructorSyntaxForNonRecordTypes = false;
inlineArrays = false;
}
}

public CSharp.LanguageVersion GetMinimumRequiredVersion()
{
if (refReadOnlyParameters || usePrimaryConstructorSyntaxForNonRecordTypes)
if (refReadOnlyParameters || usePrimaryConstructorSyntaxForNonRecordTypes || inlineArrays)
return CSharp.LanguageVersion.CSharp12_0;
if (scopedRef || requiredMembers || numericIntPtr || utf8StringLiterals || unsignedRightShift || checkedOperators)
return CSharp.LanguageVersion.CSharp11_0;
Expand Down Expand Up @@ -2053,6 +2054,24 @@ public bool UsePrimaryConstructorSyntaxForNonRecordTypes {
}
}

bool inlineArrays = true;

/// <summary>
/// Gets/Sets whether C# 12.0 inline array uses should be transformed.
/// </summary>
[Category("C# 12.0 / VS 2022.8")]
[Description("DecompilerSettings.InlineArrays")]
public bool InlineArrays {
get { return inlineArrays; }
set {
if (inlineArrays != value)
{
inlineArrays = value;
OnPropertyChanged();
}
}
}

bool separateLocalVariableDeclarations = false;

/// <summary>
Expand Down
1 change: 1 addition & 0 deletions ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
<Compile Include="DecompilationProgress.cs" />
<Compile Include="Disassembler\IEntityProcessor.cs" />
<Compile Include="Disassembler\SortByNameProcessor.cs" />
<Compile Include="IL\Transforms\InlineArrayTransform.cs" />
<Compile Include="IL\Transforms\RemoveUnconstrainedGenericReferenceTypeCheck.cs" />
<Compile Include="Metadata\MetadataFile.cs" />
<Compile Include="Metadata\ModuleReferenceMetadata.cs" />
Expand Down
Loading