Skip to content

Commit 7edda6d

Browse files
Add support for C# 12 inline array expressions
1 parent 60832e2 commit 7edda6d

14 files changed

+589
-2
lines changed

ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@
138138
<Compile Include="TestCases\ILPretty\MonoFixed.cs" />
139139
<Compile Include="TestCases\Pretty\Comparisons.cs" />
140140
<Compile Include="TestCases\Pretty\GloballyQualifiedTypeInStringInterpolation.cs" />
141+
<Compile Include="TestCases\Pretty\InlineArrayTests.cs" />
141142
<Compile Include="TestCases\Pretty\Issue3406.cs" />
142143
<Compile Include="TestCases\Pretty\PointerArithmetic.cs" />
143144
<Compile Include="TestCases\Pretty\Issue3439.cs" />

ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,12 @@ public async Task PointerArithmetic([ValueSource(nameof(defaultOptions))] Compil
749749
await RunForLibrary(cscOptions: cscOptions);
750750
}
751751

752+
[Test]
753+
public async Task InlineArrayTests([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions)
754+
{
755+
await RunForLibrary(cscOptions: cscOptions);
756+
}
757+
752758
async Task RunForLibrary([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, Action<DecompilerSettings> configureDecompiler = null)
753759
{
754760
await Run(testName, asmOptions | AssemblerOptions.Library, cscOptions | CompilerOptions.Library, configureDecompiler);
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
using System;
2+
using System.Runtime.CompilerServices;
3+
4+
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
5+
{
6+
public class InlineArrayTests
7+
{
8+
[InlineArray(16)]
9+
public struct Byte16
10+
{
11+
private byte elem;
12+
}
13+
14+
public byte Byte0()
15+
{
16+
return GetByte16()[0];
17+
}
18+
19+
public byte Byte5()
20+
{
21+
return GetByte16()[5];
22+
}
23+
24+
public byte ByteN()
25+
{
26+
return GetByte16()[GetIndex()];
27+
}
28+
29+
public byte ByteN2()
30+
{
31+
return GetByte16()[GetIndex()];
32+
}
33+
34+
public byte Byte0(Byte16 array, byte value)
35+
{
36+
return array[0] = value;
37+
}
38+
39+
public byte Byte5(Byte16 array, byte value)
40+
{
41+
return array[5] = value;
42+
}
43+
44+
public byte ByteN(Byte16 array, byte value)
45+
{
46+
return array[GetIndex()] = value;
47+
}
48+
49+
public byte VariableSplitting(Byte16 array, byte value)
50+
{
51+
return array[GetIndex()] = (array[GetIndex() + 1] = value);
52+
}
53+
54+
public void OverloadResolution()
55+
{
56+
Receiver(GetByte16());
57+
Receiver((object)GetByte16());
58+
Byte16 buffer = GetByte16();
59+
Receiver((Span<byte>)buffer);
60+
Byte16 buffer2 = GetByte16();
61+
Receiver((ReadOnlySpan<byte>)buffer2);
62+
Byte16 buffer3 = GetByte16();
63+
ReceiverSpan((Span<byte>)buffer3);
64+
Byte16 buffer4 = GetByte16();
65+
ReceiverReadOnlySpan((ReadOnlySpan<byte>)buffer4);
66+
}
67+
68+
public Byte16 GetByte16()
69+
{
70+
return default(Byte16);
71+
}
72+
73+
public int GetIndex()
74+
{
75+
return 0;
76+
}
77+
78+
public void Receiver(Span<byte> span)
79+
{
80+
}
81+
82+
public void Receiver(ReadOnlySpan<byte> span)
83+
{
84+
}
85+
86+
public void Receiver(Byte16 span)
87+
{
88+
}
89+
90+
public void Receiver(object span)
91+
{
92+
}
93+
94+
public void ReceiverSpan(Span<byte> span)
95+
{
96+
}
97+
98+
public void ReceiverReadOnlySpan(ReadOnlySpan<byte> span)
99+
{
100+
}
101+
}
102+
}

ICSharpCode.Decompiler/CSharp/CallBuilder.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,23 @@ public ExpressionWithResolveResult Build(OpCode callOpCode, IMethod method,
463463
return HandleImplicitConversion(method, argumentList.Arguments[0]);
464464
}
465465

466+
if (settings.InlineArrays
467+
&& method is { DeclaringType.FullName: "<PrivateImplementationDetails>", Name: "InlineArrayAsSpan" or "InlineArrayAsReadOnlySpan" }
468+
&& argumentList.Length == 2)
469+
{
470+
argumentList.CheckNoNamedOrOptionalArguments();
471+
var argument = argumentList.Arguments[0];
472+
var targetType = method.ReturnType;
473+
if (argument.Expression is DirectionExpression { FieldDirection: FieldDirection.In or FieldDirection.Ref, Expression: var lvalueExpr })
474+
{
475+
// `(TargetType)(in arg)` is invalid syntax.
476+
// Also, `f(in arg)` is invalid when there's an implicit conversion involved.
477+
argument = argument.UnwrapChild(lvalueExpr);
478+
}
479+
return new CastExpression(expressionBuilder.ConvertType(targetType), argument.Expression)
480+
.WithRR(new ConversionResolveResult(targetType, argument.ResolveResult, Conversion.InlineArrayConversion));
481+
}
482+
466483
if (settings.LiftNullables && method.Name == "GetValueOrDefault"
467484
&& method.DeclaringType.IsKnownType(KnownTypeCode.NullableOfT)
468485
&& method.DeclaringType.TypeArguments[0].IsKnownType(KnownTypeCode.Boolean)

ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3130,6 +3130,28 @@ protected internal override TranslatedExpression VisitLdElema(LdElema inst, Tran
31303130
.WithoutILInstruction().WithRR(new ByReferenceResolveResult(expr.ResolveResult, ReferenceKind.Ref));
31313131
}
31323132

3133+
protected internal override TranslatedExpression VisitLdElemaInlineArray(LdElemaInlineArray inst, TranslationContext context)
3134+
{
3135+
TranslatedExpression arrayExpr = TranslateTarget(
3136+
inst.Array,
3137+
nonVirtualInvocation: true,
3138+
memberStatic: false,
3139+
memberDeclaringType: inst.Type
3140+
);
3141+
var inlineArrayElementType = GetInlineArrayElementType(inst.Type.GetDefinition());
3142+
IndexerExpression indexerExpr = new IndexerExpression(
3143+
arrayExpr, inst.Indices.Select(i => TranslateArrayIndex(i).Expression)
3144+
);
3145+
TranslatedExpression expr = indexerExpr.WithILInstruction(inst).WithRR(new ResolveResult(inlineArrayElementType));
3146+
return new DirectionExpression(FieldDirection.Ref, expr)
3147+
.WithoutILInstruction().WithRR(new ByReferenceResolveResult(expr.ResolveResult, ReferenceKind.Ref));
3148+
3149+
IType GetInlineArrayElementType(ITypeDefinition arrayType)
3150+
{
3151+
return arrayType?.Fields.SingleOrDefault()?.Type ?? SpecialType.UnknownType;
3152+
}
3153+
}
3154+
31333155
TranslatedExpression TranslateArrayIndex(ILInstruction i)
31343156
{
31353157
var input = Translate(i);

ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ private Conversion ImplicitConversion(ResolveResult resolveResult, IType toType,
142142
if (c != Conversion.None)
143143
return c;
144144
}
145-
if (resolveResult is InterpolatedStringResolveResult isrr)
145+
if (resolveResult is InterpolatedStringResolveResult)
146146
{
147147
if (toType.IsKnownType(KnownTypeCode.IFormattable) || toType.IsKnownType(KnownTypeCode.FormattableString))
148148
return Conversion.ImplicitInterpolatedStringConversion;

ICSharpCode.Decompiler/DecompilerSettings.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,12 +164,13 @@ public void SetLanguageVersion(CSharp.LanguageVersion languageVersion)
164164
{
165165
refReadOnlyParameters = false;
166166
usePrimaryConstructorSyntaxForNonRecordTypes = false;
167+
inlineArrays = false;
167168
}
168169
}
169170

170171
public CSharp.LanguageVersion GetMinimumRequiredVersion()
171172
{
172-
if (refReadOnlyParameters || usePrimaryConstructorSyntaxForNonRecordTypes)
173+
if (refReadOnlyParameters || usePrimaryConstructorSyntaxForNonRecordTypes || inlineArrays)
173174
return CSharp.LanguageVersion.CSharp12_0;
174175
if (scopedRef || requiredMembers || numericIntPtr || utf8StringLiterals || unsignedRightShift || checkedOperators)
175176
return CSharp.LanguageVersion.CSharp11_0;
@@ -2053,6 +2054,24 @@ public bool UsePrimaryConstructorSyntaxForNonRecordTypes {
20532054
}
20542055
}
20552056

2057+
bool inlineArrays = true;
2058+
2059+
/// <summary>
2060+
/// Gets/Sets whether C# 12.0 inline array uses should be transformed.
2061+
/// </summary>
2062+
[Category("C# 12.0 / VS 2022.8")]
2063+
[Description("DecompilerSettings.InlineArrays")]
2064+
public bool InlineArrays {
2065+
get { return inlineArrays; }
2066+
set {
2067+
if (inlineArrays != value)
2068+
{
2069+
inlineArrays = value;
2070+
OnPropertyChanged();
2071+
}
2072+
}
2073+
}
2074+
20562075
bool separateLocalVariableDeclarations = false;
20572076

20582077
/// <summary>

ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
<Compile Include="DecompilationProgress.cs" />
108108
<Compile Include="Disassembler\IEntityProcessor.cs" />
109109
<Compile Include="Disassembler\SortByNameProcessor.cs" />
110+
<Compile Include="IL\Transforms\InlineArrayTransform.cs" />
110111
<Compile Include="IL\Transforms\RemoveUnconstrainedGenericReferenceTypeCheck.cs" />
111112
<Compile Include="Metadata\MetadataFile.cs" />
112113
<Compile Include="Metadata\ModuleReferenceMetadata.cs" />

0 commit comments

Comments
 (0)