diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 1c3c1f7f9..dd58a896b 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -3880,7 +3880,7 @@ namespace ICSharpCode.Decompiler.CSharp TranslatedExpression TranslateArrayInitializer(Block block) { var stloc = block.Instructions.FirstOrDefault() as StLoc; - var final = block.FinalInstruction as LdLoc; + var final = Block.MatchArrayInitializerFinal(block.FinalInstruction, out var arrayToSpan); if (stloc == null || final == null || !stloc.Value.MatchNewArr(out IType? type)) throw new ArgumentException("given Block is invalid!"); if (stloc.Variable != final.Variable || stloc.Variable.Kind != VariableKind.InitializerTarget) @@ -3961,8 +3961,18 @@ namespace ICSharpCode.Decompiler.CSharp expr.AdditionalArraySpecifiers.AddRange(additionalSpecifiers); if (!type.ContainsAnonymousType()) expr.Arguments.AddRange(newArr.Indices.Select(i => Translate(i).Expression)); - return expr.WithILInstruction(block) - .WithRR(new ArrayCreateResolveResult(new ArrayType(compilation, type, dimensions), newArr.Indices.Select(i => Translate(i).ResolveResult).ToArray(), elementResolveResults)); + ResolveResult rr = new ArrayCreateResolveResult(new ArrayType(compilation, type, dimensions), + newArr.Indices.Select(i => Translate(i).ResolveResult).ToArray(), elementResolveResults); + if (arrayToSpan != null) + { + // The block converts its array to Span/ReadOnlySpan. The conversion is + // implicit in C#, so the array initializer stands on its own, but the expression + // still has the span type: the block evaluates to a span, not to an array. + rr = new ConversionResolveResult(arrayToSpan.ReturnType, rr, + Conversion.UserDefinedConversion(arrayToSpan, isImplicit: true, + Conversion.IdentityConversion, Conversion.IdentityConversion)); + } + return expr.WithILInstruction(block).WithRR(rr); } TranslatedExpression TranslateStackAllocInitializer(Block block, IType typeHint) diff --git a/ICSharpCode.Decompiler/IL/Instructions/Block.cs b/ICSharpCode.Decompiler/IL/Instructions/Block.cs index b9ba2227f..af03ca0c7 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/Block.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/Block.cs @@ -103,6 +103,26 @@ namespace ICSharpCode.Decompiler.IL return clone; } + /// + /// The load of the initializer target an ArrayInitializer block evaluates to. The block + /// may end in the implicit conversion of that array to Span<T>/ReadOnlySpan<T>, + /// which is returned in ; everything else about the block is + /// the same in both shapes. + /// + internal static LdLoc? MatchArrayInitializerFinal(ILInstruction finalInstruction, out IMethod? conversion) + { + conversion = null; + if (finalInstruction is CallInstruction { Arguments.Count: 1 } call + && call.Method.IsOperator && call.Method.Name == "op_Implicit" + && (call.Method.ReturnType.IsKnownType(KnownTypeCode.SpanOfT) + || call.Method.ReturnType.IsKnownType(KnownTypeCode.ReadOnlySpanOfT))) + { + conversion = call.Method; + finalInstruction = call.Arguments[0]; + } + return finalInstruction as LdLoc; + } + internal override void CheckInvariant(ILPhase phase, ICompilation compilation) { base.CheckInvariant(phase, compilation); @@ -138,7 +158,7 @@ namespace ICSharpCode.Decompiler.IL } break; case BlockKind.ArrayInitializer: - var final = finalInstruction as LdLoc; + var final = MatchArrayInitializerFinal(finalInstruction, out _); Debug.Assert(final != null && final.Variable.IsSingleDefinition && final.Variable.Kind == VariableKind.InitializerTarget); IType? type = null; Debug.Assert(Instructions[0].MatchStLoc(final!.Variable, out var init) && init.MatchNewArr(out type)); diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs index 0dfbdc207..0f1dc9ab1 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs @@ -19,6 +19,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Collections.Immutable; using System.Reflection.Metadata; using System.Text; @@ -68,7 +69,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms { context.Step("HandleRuntimeHelperInitializeArray: single-dim", inst); var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type); - var block = BlockFromInitializer(tempStore, elementType, arrayLength, values); + var block = BlockFromInitializer(tempStore, elementType, null, arrayLength, values); var newStore = new StLoc(v, block); body.Instructions[pos] = newStore; body.Instructions.RemoveAt(initArrayPos); @@ -160,24 +161,20 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (DecodeArrayInitializer(elementType, initialValue, new[] { size }, valuesList)) { var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, new ArrayType(context.TypeSystem, elementType)); - ILInstruction result = BlockFromInitializer(tempStore, elementType, new[] { size }, valuesList.ToArray()); + IMethod op_Implicit = null; 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 } }; + // ends in the conversion the C# compiler would have applied. + op_Implicit = context.TypeSystem.MainModule.ResolveMethod(targetType, "op_Implicit", + new MethodSignature( + new SignatureHeader(SignatureKind.Method, SignatureCallingConvention.Default, SignatureAttributes.None), + returnType: targetType, + requiredParameterCount: 1, + genericParameterCount: 0, + parameterTypes: ImmutableArray.Create(new ArrayType(context.TypeSystem, elementType)))); } - return result; + return BlockFromInitializer(tempStore, elementType, op_Implicit, new[] { size }, valuesList.ToArray()); } return null; @@ -260,7 +257,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (HandleRuntimeHelpersInitializeArray(body, pos + 1, v, elementType, length, out var values, out var initArrayPos)) { context.Step("HandleRuntimeHelpersInitializeArray: multi-dim", inst); - var block = BlockFromInitializer(v, elementType, length, values); + var block = BlockFromInitializer(v, elementType, null, length, values); var newStore = new StLoc(v, block); body.Instructions[pos].ReplaceWith(newStore); body.Instructions.RemoveAt(initArrayPos); @@ -837,7 +834,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms && initializer.OpCode == OpCode.Block; } - static Block BlockFromInitializer(ILVariable v, IType elementType, int[] arrayLength, ILInstruction[] values) + static Block BlockFromInitializer(ILVariable v, IType elementType, IMethod arrayToSpan, int[] arrayLength, ILInstruction[] values) { var block = new Block(BlockKind.ArrayInitializer); block.Instructions.Add(new StLoc(v, new NewArr(elementType, arrayLength.Select(l => new LdcI4(l)).ToArray()))); @@ -858,7 +855,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms block.Instructions.Add(StElem(new LdLoc(v), indices.ToArray(), value, elementType)); indices.Clear(); } - block.FinalInstruction = new LdLoc(v); + ILInstruction final = new LdLoc(v); + if (arrayToSpan != null) + { + final = new Call(arrayToSpan) { Arguments = { final } }; + } + block.FinalInstruction = final; return block; } @@ -966,7 +968,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; context.Step("InlineRuntimeHelpersInitializeArray: single-dim", inst); var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, new ArrayType(context.TypeSystem, elementType, arrayLength.Length)); - var block = BlockFromInitializer(tempStore, elementType, arrayLength, valuesList.ToArray()); + var block = BlockFromInitializer(tempStore, elementType, null, arrayLength, valuesList.ToArray()); body.Instructions[pos] = block; context.EndStep(block); ILInlining.InlineIfPossible(body, pos, context); diff --git a/ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs b/ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs index e2f094fa6..a48bc1631 100644 --- a/ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs +++ b/ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs @@ -568,19 +568,7 @@ namespace ICSharpCode.Decompiler.TypeSystem parameterTypes = signature.ParameterTypes; } // Search for the matching method: - method = null; - foreach (var m in methods) - { - if (m.TypeParameters.Count != signature.GenericParameterCount) - continue; - if (signature.Header.IsInstance != !m.IsStatic) - continue; - if (CompareSignatures(m.Parameters, parameterTypes) && CompareTypes(m.ReturnType, signature.ReturnType)) - { - method = m; - break; - } - } + method = FindMethod(methods, signature, parameterTypes); } else { @@ -602,6 +590,60 @@ namespace ICSharpCode.Decompiler.TypeSystem return method; } + /// + /// Resolves a method on by name and signature. + /// If the type declares no such method - because the reference is missing, or the + /// method does not exist on the version at hand - a fake method carrying the requested + /// signature is returned, as for a method reference that cannot be resolved. + /// + /// + /// The signature is matched against the members of as + /// they are seen from the outside, so for a parameterized type it is written in terms of + /// the type arguments, not the type parameters. This is the lookup a decompiler step + /// needs when it has to name a specific method - a conversion operator, say - rather + /// than one it read from metadata. + /// + public IMethod ResolveMethod(IType declaringType, string name, MethodSignature signature) + { + if (declaringType == null) + throw new ArgumentNullException(nameof(declaringType)); + if (name == null) + throw new ArgumentNullException(nameof(name)); + IEnumerable methods; + if (name == ".ctor") + { + methods = declaringType.GetConstructors(); + } + else + { + methods = declaringType.GetMethods(m => m.Name == name) + .Concat(declaringType.GetAccessors(m => m.Name == name)); + } + return FindMethod(methods, signature, signature.ParameterTypes) + ?? CreateFakeMethod(declaringType, name, signature); + } + + /// + /// The single method among that matches the signature, or + /// null. is passed separately because a vararg + /// signature is matched against its required parameters plus __arglist. + /// + static IMethod FindMethod(IEnumerable candidates, MethodSignature signature, ImmutableArray parameterTypes) + { + foreach (var method in candidates) + { + if (method.TypeParameters.Count != signature.GenericParameterCount) + continue; + if (signature.Header.IsInstance != !method.IsStatic) + continue; + if (CompareSignatures(method.Parameters, parameterTypes) && CompareTypes(method.ReturnType, signature.ReturnType)) + { + return method; + } + } + return null; + } + static readonly NormalizeTypeVisitor normalizeTypeVisitor = new NormalizeTypeVisitor { ReplaceClassTypeParametersWithDummy = true, ReplaceMethodTypeParametersWithDummy = true,