Skip to content

Commit 2c925f4

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

File tree

14 files changed

+625
-2
lines changed

14 files changed

+625
-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: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
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+
[InlineArray(16)]
15+
public struct Generic16<T>
16+
{
17+
private T elem;
18+
}
19+
20+
public byte Byte0()
21+
{
22+
return GetByte16()[0];
23+
}
24+
25+
public byte GenericByte0()
26+
{
27+
return GetGeneric<byte>()[0];
28+
}
29+
30+
public byte Byte5()
31+
{
32+
return GetByte16()[5];
33+
}
34+
35+
public byte GenericByte5()
36+
{
37+
return GetGeneric<byte>()[5];
38+
}
39+
40+
public byte ByteN()
41+
{
42+
return GetByte16()[GetIndex()];
43+
}
44+
45+
public byte GenericByteN()
46+
{
47+
return GetGeneric<byte>()[GetIndex()];
48+
}
49+
50+
public byte Byte0(Byte16 array, byte value)
51+
{
52+
return array[0] = value;
53+
}
54+
55+
public byte GenericByte0(Generic16<byte> array, byte value)
56+
{
57+
return array[0] = value;
58+
}
59+
60+
public byte Byte5(Byte16 array, byte value)
61+
{
62+
return array[5] = value;
63+
}
64+
65+
public byte GenericByte5(Generic16<byte> array, byte value)
66+
{
67+
return array[5] = value;
68+
}
69+
70+
public byte ByteN(Byte16 array, byte value)
71+
{
72+
return array[GetIndex()] = value;
73+
}
74+
75+
public byte GenericByteN(Generic16<byte> array, byte value)
76+
{
77+
return array[GetIndex()] = value;
78+
}
79+
80+
public byte VariableSplitting(Byte16 array, byte value)
81+
{
82+
return array[GetIndex()] = (array[GetIndex() + 1] = value);
83+
}
84+
85+
public void OverloadResolution()
86+
{
87+
Receiver(GetByte16());
88+
Receiver((object)GetByte16());
89+
Byte16 buffer = GetByte16();
90+
Receiver((Span<byte>)buffer);
91+
Byte16 buffer2 = GetByte16();
92+
Receiver((ReadOnlySpan<byte>)buffer2);
93+
Byte16 buffer3 = GetByte16();
94+
ReceiverSpan((Span<byte>)buffer3);
95+
Byte16 buffer4 = GetByte16();
96+
ReceiverReadOnlySpan((ReadOnlySpan<byte>)buffer4);
97+
}
98+
99+
public Byte16 GetByte16()
100+
{
101+
return default(Byte16);
102+
}
103+
104+
public Generic16<T> GetGeneric<T>()
105+
{
106+
return default(Generic16<T>);
107+
}
108+
109+
public int GetIndex()
110+
{
111+
return 0;
112+
}
113+
114+
public void Receiver(Span<byte> span)
115+
{
116+
}
117+
118+
public void Receiver(ReadOnlySpan<byte> span)
119+
{
120+
}
121+
122+
public void Receiver(Byte16 span)
123+
{
124+
}
125+
126+
public void Receiver(object span)
127+
{
128+
}
129+
130+
public void ReceiverSpan(Span<byte> span)
131+
{
132+
}
133+
134+
public void ReceiverReadOnlySpan(ReadOnlySpan<byte> span)
135+
{
136+
}
137+
}
138+
}

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);
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(IType arrayType)
3150+
{
3151+
return arrayType?.GetFields(f => !f.IsStatic).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)