Skip to content

Commit 83c34d4

Browse files
Merge pull request #3467 from icsharpcode/feature/inlinearrays
Part 1 of support for C# 12 InlineArray
2 parents fac0e5e + 298c247 commit 83c34d4

File tree

18 files changed

+760
-8
lines changed

18 files changed

+760
-8
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@
140140
<Compile Include="TestCases\ILPretty\MonoFixed.cs" />
141141
<Compile Include="TestCases\Pretty\Comparisons.cs" />
142142
<Compile Include="TestCases\Pretty\GloballyQualifiedTypeInStringInterpolation.cs" />
143+
<Compile Include="TestCases\Pretty\InlineArrayTests.cs" />
143144
<Compile Include="TestCases\Pretty\Issue3406.cs" />
144145
<Compile Include="TestCases\Pretty\Issue3439.cs" />
145146
<Compile Include="TestCases\Pretty\Issue3442.cs" />

ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

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

758+
[Test]
759+
public async Task InlineArrayTests([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions)
760+
{
761+
await RunForLibrary(cscOptions: cscOptions);
762+
}
763+
758764
async Task RunForLibrary([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, Action<DecompilerSettings> configureDecompiler = null)
759765
{
760766
await Run(testName, asmOptions | AssemblerOptions.Library, cscOptions | CompilerOptions.Library, configureDecompiler);

ICSharpCode.Decompiler.Tests/TestCases/Correctness/Conversions.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
// #include "../../../ICSharpCode.Decompiler/Util/CSharpPrimitiveCast.cs"
2020

2121
using System;
22+
using System.Runtime.CompilerServices;
2223

2324
using ICSharpCode.Decompiler.Util;
2425

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

112113
Console.WriteLine(ReadZeroTerminatedString("Hello World!".Length));
113114
C1.Test();
115+
#if ROSLYN2 && !NET40
116+
C3.Run();
117+
#endif
114118
}
115119

116120
static void RunTest(bool checkForOverflow)
@@ -199,4 +203,32 @@ public static implicit operator C1(C2 c)
199203
return new C1();
200204
}
201205
}
206+
207+
#if ROSLYN2 && !NET40
208+
class C3
209+
{
210+
[InlineArray(4)] struct MyArray { private int elem; }
211+
212+
static void Foo(object o)
213+
{
214+
Console.WriteLine("Foo(object) called");
215+
}
216+
217+
static void Foo(ReadOnlySpan<int> o)
218+
{
219+
Console.WriteLine("Foo(ReadOnlySpan<int>) called");
220+
}
221+
222+
static void Test(MyArray arr)
223+
{
224+
Foo((object)arr);
225+
}
226+
227+
public static void Run()
228+
{
229+
Console.WriteLine("C3.Run() called");
230+
Test(default);
231+
}
232+
}
233+
#endif
202234
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
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 void Slice(Byte16 array)
81+
{
82+
Receiver(array[..8]);
83+
Receiver((ReadOnlySpan<byte>)array[..8]);
84+
ReceiverSpan(array[..8]);
85+
ReceiverReadOnlySpan(array[..8]);
86+
}
87+
88+
// TODO
89+
//public void Slice(Byte16 array, int end)
90+
//{
91+
// Receiver(array[..end]);
92+
// Receiver((ReadOnlySpan<byte>)array[..end]);
93+
// ReceiverSpan(array[..end]);
94+
// ReceiverReadOnlySpan(array[..end]);
95+
//}
96+
97+
public byte VariableSplitting(Byte16 array, byte value)
98+
{
99+
return array[GetIndex()] = (array[GetIndex() + 1] = value);
100+
}
101+
102+
public void OverloadResolution()
103+
{
104+
Receiver(GetByte16());
105+
Receiver((object)GetByte16());
106+
Byte16 buffer = GetByte16();
107+
Receiver((Span<byte>)buffer);
108+
Byte16 buffer2 = GetByte16();
109+
Receiver((ReadOnlySpan<byte>)buffer2);
110+
Byte16 buffer3 = GetByte16();
111+
ReceiverSpan(buffer3);
112+
Byte16 buffer4 = GetByte16();
113+
ReceiverReadOnlySpan(buffer4);
114+
}
115+
116+
public Byte16 GetByte16()
117+
{
118+
return default(Byte16);
119+
}
120+
121+
public Generic16<T> GetGeneric<T>()
122+
{
123+
return default(Generic16<T>);
124+
}
125+
126+
public int GetIndex()
127+
{
128+
return 0;
129+
}
130+
131+
public void Receiver(Span<byte> span)
132+
{
133+
}
134+
135+
public void Receiver(ReadOnlySpan<byte> span)
136+
{
137+
}
138+
139+
public void Receiver(Byte16 span)
140+
{
141+
}
142+
143+
public void Receiver(object span)
144+
{
145+
}
146+
147+
public void ReceiverSpan(Span<byte> span)
148+
{
149+
}
150+
151+
public void ReceiverReadOnlySpan(ReadOnlySpan<byte> span)
152+
{
153+
}
154+
}
155+
}

ICSharpCode.Decompiler/CSharp/CallBuilder.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,42 @@ 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 arrayType = method.TypeArguments[0];
472+
var arrayLength = arrayType.GetInlineArrayLength();
473+
var arrayElementType = arrayType.GetInlineArrayElementType();
474+
var argument = argumentList.Arguments[0];
475+
var spanLengthExpr = argumentList.Arguments[1];
476+
var targetType = method.ReturnType;
477+
var spanType = typeSystem.FindType(KnownTypeCode.SpanOfT);
478+
if (argument.Expression is DirectionExpression { FieldDirection: FieldDirection.In or FieldDirection.Ref, Expression: var lvalueExpr })
479+
{
480+
// `(TargetType)(in arg)` is invalid syntax.
481+
// Also, `f(in arg)` is invalid when there's an implicit conversion involved.
482+
argument = argument.UnwrapChild(lvalueExpr);
483+
}
484+
if (spanLengthExpr.ResolveResult.ConstantValue is int spanLength && spanLength <= arrayLength)
485+
{
486+
if (spanLength < arrayLength)
487+
{
488+
argument = new IndexerExpression(argument.Expression, new BinaryOperatorExpression {
489+
Operator = BinaryOperatorType.Range,
490+
Right = spanLengthExpr.Expression
491+
}).WithRR(new ResolveResult(new ParameterizedType(spanType, arrayElementType))).WithoutILInstruction();
492+
if (targetType.IsKnownType(KnownTypeCode.SpanOfT))
493+
{
494+
return argument;
495+
}
496+
}
497+
return new CastExpression(expressionBuilder.ConvertType(targetType), argument.Expression)
498+
.WithRR(new ConversionResolveResult(targetType, argument.ResolveResult, Conversion.InlineArrayConversion));
499+
}
500+
}
501+
466502
if (settings.LiftNullables && method.Name == "GetValueOrDefault"
467503
&& method.DeclaringType.IsKnownType(KnownTypeCode.NullableOfT)
468504
&& method.DeclaringType.TypeArguments[0].IsKnownType(KnownTypeCode.Boolean)

ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3130,6 +3130,23 @@ 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 = inst.Type.GetInlineArrayElementType();
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+
31333150
TranslatedExpression TranslateArrayIndex(ILInstruction i)
31343151
{
31353152
var input = Translate(i);

ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs

Lines changed: 10 additions & 2 deletions
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;
@@ -230,12 +230,20 @@ Conversion StandardImplicitConversion(IType fromType, IType toType, bool allowTu
230230
if (c != Conversion.None)
231231
return c;
232232
}
233+
if ((toType.IsKnownType(KnownTypeCode.SpanOfT) || toType.IsKnownType(KnownTypeCode.ReadOnlySpanOfT))
234+
&& fromType.IsInlineArrayType())
235+
{
236+
var elementType = fromType.GetInlineArrayElementType();
237+
var spanElementType = toType.TypeArguments[0];
238+
if (IdentityConversion(elementType, spanElementType))
239+
return Conversion.InlineArrayConversion;
240+
}
233241
return Conversion.None;
234242
}
235243

236244
/// <summary>
237245
/// Gets whether the type 'fromType' is convertible to 'toType'
238-
/// using one of the conversions allowed when satisying constraints (§4.4.4)
246+
/// using one of the conversions allowed when satisfying constraints (§4.4.4)
239247
/// </summary>
240248
public bool IsConstraintConvertible(IType fromType, IType toType)
241249
{

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)