Skip to content

Commit 7c47d22

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

File tree

10 files changed

+171
-2
lines changed

10 files changed

+171
-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: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using System.Runtime.CompilerServices;
2+
3+
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
4+
{
5+
public class InlineArrayTests
6+
{
7+
[InlineArray(16)]
8+
public struct Byte16
9+
{
10+
private byte elem;
11+
}
12+
13+
[InlineArray(4)]
14+
public struct Int128
15+
{
16+
private int elem;
17+
}
18+
19+
public byte Byte0()
20+
{
21+
return GetByte16()[0];
22+
}
23+
24+
public byte Byte5()
25+
{
26+
return GetByte16()[5];
27+
}
28+
29+
public byte ByteN()
30+
{
31+
return GetByte16()[GetIndex()];
32+
}
33+
34+
public byte ByteN2()
35+
{
36+
return GetByte16()[GetIndex()];
37+
}
38+
39+
public byte Byte0(Byte16 array, byte value)
40+
{
41+
return array[0] = value;
42+
}
43+
44+
public byte Byte5(Byte16 array, byte value)
45+
{
46+
return array[5] = value;
47+
}
48+
49+
public byte ByteN(Byte16 array, byte value)
50+
{
51+
return array[GetIndex()] = value;
52+
}
53+
54+
public byte VariableSplitting(Byte16 array, byte value)
55+
{
56+
return array[GetIndex()] = (array[GetIndex() + 1] = value);
57+
}
58+
59+
public Byte16 GetByte16()
60+
{
61+
return default(Byte16);
62+
}
63+
64+
public int GetIndex()
65+
{
66+
return 0;
67+
}
68+
}
69+
}

ICSharpCode.Decompiler/CSharp/Annotations.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,10 @@ public static ISymbol GetSymbol(this AstNode node)
159159
return ldVirtDelegate.Method;
160160
}
161161
}
162+
if (rr is InlineArrayResolveResult iarr)
163+
{
164+
return iarr.ArrayType.GetSymbol();
165+
}
162166
return rr?.GetSymbol();
163167
}
164168

ICSharpCode.Decompiler/CSharp/CallBuilder.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,29 @@ public ExpressionWithResolveResult Build(OpCode callOpCode, IMethod method,
435435
argumentList.GetArgumentResolveResults(), isExpandedForm: argumentList.IsExpandedForm, isDelegateInvocation: true));
436436
}
437437

438+
if (settings.InlineArrays && method.DeclaringType.FullName == "<PrivateImplementationDetails>")
439+
{
440+
var unwrappedTarget = argumentList.Arguments[0].Expression;
441+
if (unwrappedTarget is DirectionExpression dirExpr)
442+
unwrappedTarget = dirExpr.Expression;
443+
switch (method.Name)
444+
{
445+
case "InlineArrayAsSpan":
446+
case "InlineArrayAsReadOnlySpan":
447+
var arrayResolveResult = unwrappedTarget.GetResolveResult();
448+
unwrappedTarget.RemoveAnnotations<ResolveResult>();
449+
return unwrappedTarget.Detach().WithRR(new InlineArrayResolveResult(arrayResolveResult, method.ReturnType));
450+
case "InlineArrayFirstElementRef":
451+
case "InlineArrayFirstElementRefReadOnly":
452+
return new IndexerExpression(unwrappedTarget.Detach(), new PrimitiveExpression(0))
453+
.WithRR(new ResolveResult(method.ReturnType.UnwrapByRef()));
454+
case "InlineArrayElementRef":
455+
case "InlineArrayElementRefReadOnly":
456+
return new IndexerExpression(unwrappedTarget.Detach(), argumentList.Arguments[1])
457+
.WithRR(new ResolveResult(method.ReturnType.UnwrapByRef()));
458+
}
459+
}
460+
438461
if (settings.StringInterpolation && IsInterpolatedStringCreation(method, argumentList))
439462
{
440463
var result = HandleStringInterpolation(method, argumentList);

ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,16 @@ 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;
149149
}
150+
if (resolveResult is InlineArrayResolveResult)
151+
{
152+
if (toType.Equals(resolveResult.Type))
153+
return Conversion.InlineArrayConversion;
154+
}
150155
if (resolveResult.Type.Kind == TypeKind.Dynamic)
151156
return Conversion.ImplicitDynamicConversion;
152157
c = AnonymousFunctionConversion(resolveResult, toType);

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
@@ -153,6 +153,7 @@
153153
<Compile Include="Metadata\MetadataGenericContext.cs" />
154154
<Compile Include="Metadata\ReferenceLoadInfo.cs" />
155155
<Compile Include="Properties\DecompilerVersionInfo.cs" />
156+
<Compile Include="Semantics\InlineArrayResolveResult.cs" />
156157
<Compile Include="TypeSystem\ITypeDefinitionOrUnknown.cs" />
157158
<Compile Include="Util\BitOperations.cs" />
158159
<Compile Include="Util\DelegateComparer.cs" />

ICSharpCode.Decompiler/Semantics/Conversion.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ public static Conversion EnumerationConversion(bool isImplicit, bool isLifted)
8787
/// </summary>
8888
public static readonly Conversion ThrowExpressionConversion = new BuiltinConversion(true, 11);
8989

90+
/// <summary>
91+
/// C# 12 inline array implicitly being converted to <see cref="System.Span{T}"/> or <see cref="System.ReadOnlySpan{T}"/>.
92+
/// </summary>
93+
public static readonly Conversion InlineArrayConversion = new BuiltinConversion(true, 12);
94+
9095
public static Conversion UserDefinedConversion(IMethod operatorMethod, bool isImplicit, Conversion conversionBeforeUserDefinedOperator, Conversion conversionAfterUserDefinedOperator, bool isLifted = false, bool isAmbiguous = false)
9196
{
9297
if (operatorMethod == null)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright (c) 2025 Siegfried Pammer
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
4+
// software and associated documentation files (the "Software"), to deal in the Software
5+
// without restriction, including without limitation the rights to use, copy, modify, merge,
6+
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7+
// to whom the Software is furnished to do so, subject to the following conditions:
8+
//
9+
// The above copyright notice and this permission notice shall be included in all copies or
10+
// substantial portions of the Software.
11+
//
12+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14+
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15+
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17+
// DEALINGS IN THE SOFTWARE.
18+
19+
using System.Diagnostics;
20+
21+
using ICSharpCode.Decompiler.TypeSystem;
22+
23+
namespace ICSharpCode.Decompiler.Semantics
24+
{
25+
public class InlineArrayResolveResult : ResolveResult
26+
{
27+
public InlineArrayResolveResult(ResolveResult arrayType, IType spanType)
28+
: base(spanType)
29+
{
30+
Debug.Assert(spanType.IsKnownType(KnownTypeCode.SpanOfT) || spanType.IsKnownType(KnownTypeCode.ReadOnlySpanOfT));
31+
ArrayType = arrayType;
32+
}
33+
34+
public ResolveResult ArrayType { get; }
35+
}
36+
}

0 commit comments

Comments
 (0)