From 6de4c1d5b5c5d334042d90bf77641439d19a1d25 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 5 Sep 2026 12:40:59 +0200 Subject: [PATCH] TransformArrayInitializers: convert the array initializer to the span The Span/ReadOnlySpan initializer patterns replaced their call with an array initializer block, so an array stood where a span was expected: the enclosing leave and any call taking the result saw StackType.Obj against the StackType.VT the span type demands. The conversion the C# compiler applies is the implicit operator, and it has to wrap the block rather than sit inside it, because an ArrayInitializer block must keep ldloc as its final instruction. Without the operator the conversion cannot be expressed at all, so the original call is left untransformed instead. Assisted-by: Claude:claude-opus-5[1m]:Claude Code --- .../Transforms/TransformArrayInitializers.cs | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs index b3a76661e..0dfbdc207 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs @@ -118,7 +118,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!field.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA)) return false; var initialValue = field.GetInitialValue(context.PEFile, context.TypeSystem); - replacement = DecodeArrayInitializerOrUTF8StringLiteral(context, elementType, initialValue, size); + replacement = DecodeArrayInitializerOrUTF8StringLiteral(context, elementType, inst.Method.DeclaringType, initialValue, size); return replacement != null; } @@ -138,7 +138,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (elementTypeSize <= 0 || initialValue.Length % elementTypeSize != 0) return false; var size = initialValue.Length / elementTypeSize; - replacement = DecodeArrayInitializerOrUTF8StringLiteral(context, elementType, initialValue, size); + replacement = DecodeArrayInitializerOrUTF8StringLiteral(context, elementType, inst.Method.ReturnType, initialValue, size); return replacement != null; } @@ -149,7 +149,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return MatchGetStaticFieldAddress(get_Item, out _); } - private static ILInstruction DecodeArrayInitializerOrUTF8StringLiteral(StatementTransformContext context, IType elementType, BlobReader initialValue, int size) + private static ILInstruction DecodeArrayInitializerOrUTF8StringLiteral(StatementTransformContext context, IType elementType, IType targetType, BlobReader initialValue, int size) { if (context.Settings.Utf8StringLiterals && elementType.IsKnownType(KnownTypeCode.Byte) && DecodeUTF8String(initialValue, size, out string text)) @@ -160,7 +160,24 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (DecodeArrayInitializer(elementType, initialValue, new[] { size }, valuesList)) { var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, new ArrayType(context.TypeSystem, elementType)); - return BlockFromInitializer(tempStore, elementType, new[] { size }, valuesList.ToArray()); + ILInstruction result = BlockFromInitializer(tempStore, elementType, new[] { size }, valuesList.ToArray()); + if (targetType.IsKnownType(KnownTypeCode.SpanOfT) || targetType.IsKnownType(KnownTypeCode.ReadOnlySpanOfT)) + { + // The block builds an array where a Span/ReadOnlySpan is expected, so it + // needs the conversion the C# compiler would have applied. It has to wrap the + // block: an ArrayInitializer block must keep ldloc as its final instruction. + var op_Implicit = targetType.GetMethods(m => m.IsOperator && m.Name == "op_Implicit" + && m.Parameters.Count == 1 + && m.Parameters[0].Type.Kind == TypeKind.Array).FirstOrDefault(); + if (op_Implicit == null) + { + // Without the operator the conversion cannot be expressed; leave the + // original call alone rather than produce an array where a span belongs. + return null; + } + result = new Call(op_Implicit) { Arguments = { result } }; + } + return result; } return null;