From eae54ddf241af05a68f8c516d076ab82557f53bf Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Wed, 1 Nov 2023 08:29:03 +0100 Subject: [PATCH] Fix #3014: Missing type information in lambda expressions. --- .../TestCases/Pretty/ExpressionTrees.cs | 12 ++++----- .../TestCases/Pretty/FixProxyCalls.cs | 2 +- .../TestCases/Pretty/TupleTests.cs | 25 +++++++++++++++++-- ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 17 ++++++++++--- .../CSharp/ExpressionBuilder.cs | 3 +-- .../CSharp/StatementBuilder.cs | 19 ++++++++++++++ .../CSharp/TranslatedExpression.cs | 15 ++++++++--- .../TypeSystem/NormalizeTypeVisitor.cs | 11 ++++++++ .../Util/CollectionExtensions.cs | 6 +++++ 9 files changed, 92 insertions(+), 18 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs index 427d57153a..039804db78 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs @@ -223,15 +223,15 @@ public SimpleTypeWithMultipleCtors(int i) private dynamic ViewBag; public static readonly object[] SupportedMethods = new object[2] { - ToCode(null, () => ((IQueryable)null).Aggregate((object o1, object o2) => null)), - ToCode(null, () => ((IEnumerable)null).Aggregate((object o1, object o2) => null)) + ToCode(null, () => ((IQueryable)null).Aggregate((object o1, object o2) => (object)null)), + ToCode(null, () => ((IEnumerable)null).Aggregate((object o1, object o2) => (object)null)) }; public static readonly object[] SupportedMethods2 = new object[4] { - ToCode(null, () => ((IQueryable)null).Aggregate(null, (object o1, object o2) => null)), - ToCode(null, () => ((IQueryable)null).Aggregate((object)null, (Expression>)((object o1, object o2) => null), (Expression>)((object o) => null))), - ToCode(null, () => ((IEnumerable)null).Aggregate(null, (object o1, object o2) => null)), - ToCode(null, () => ((IEnumerable)null).Aggregate((object)null, (Func)((object o1, object o2) => null), (Func)((object o) => null))) + ToCode(null, () => ((IQueryable)null).Aggregate(null, (object o1, object o2) => (object)null)), + ToCode(null, () => ((IQueryable)null).Aggregate(null, (object o1, object o2) => (object)null, (object o) => (object)null)), + ToCode(null, () => ((IEnumerable)null).Aggregate(null, (object o1, object o2) => (object)null)), + ToCode(null, () => ((IEnumerable)null).Aggregate(null, (object o1, object o2) => (object)null, (object o) => (object)null)) }; public static void TestCall(object a) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FixProxyCalls.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FixProxyCalls.cs index 0675b77781..4b14803d76 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FixProxyCalls.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FixProxyCalls.cs @@ -120,7 +120,7 @@ public class Issue1660 : Issue1660Base public Action M(object state) { return delegate (object x) { - base.BaseCall(x, state, (Func)(() => null)); + base.BaseCall(x, state, () => (object)null); }; } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.cs index f0e19c1ba4..90692b1fff 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.cs @@ -77,7 +77,7 @@ public struct GenericStruct public int AccessPartiallyNamed => PartiallyNamed.a + PartiallyNamed.Item3; public ValueTuple NewTuple1 => new ValueTuple(1); - public (int a, int b) NewTuple2 => (1, 2); + public (int a, int b) NewTuple2 => (a: 1, b: 2); public object BoxedTuple10 => (1, 2, 3, 4, 5, 6, 7, 8, 9, 10); public (uint, int) SwapUnnamed => (Unnamed2.Item2, Unnamed2.Item1); @@ -115,7 +115,7 @@ public void UnnamedTupleRef(ref (int, string, Action, dynamic) tuple) public void NamedTupleOut(out (int A, string B, Action C, dynamic D) tuple) { - tuple = (42, "Hello", Console.WriteLine, null); + tuple = (A: 42, B: "Hello", C: Console.WriteLine, D: null); } public void NamedTupleIn(in (int A, string B, Action C, dynamic D) tuple) @@ -141,6 +141,27 @@ public void UseDict() Console.WriteLine(TupleDict.Values.ToList().First().d); } + private static (string, string) Issue3014a(string[] args) + { + return (from v in args + select (Name: v, Value: v) into kvp + orderby kvp.Name + select kvp).First(); + } + + private static (string, string) Issue3014b(string[] args) + { + return (from v in args + select ((string Name, string Value))GetTuple() into kvp + orderby kvp.Name + select kvp).First(); + + (string, string) GetTuple() + { + return (args[0], args[1]); + } + } + public void Issue1174() { Console.WriteLine((1, 2, 3).GetHashCode()); diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index d0f4c056c0..d34ec1cfd1 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -189,7 +189,7 @@ public CallBuilder(ExpressionBuilder expressionBuilder, IDecompilerTypeSystem ty this.typeSystem = typeSystem; } - public TranslatedExpression Build(CallInstruction inst) + public TranslatedExpression Build(CallInstruction inst, IType typeHint = null) { if (inst is NewObj newobj && IL.Transforms.DelegateConstruction.MatchDelegateConstruction(newobj, out _, out _, out _)) { @@ -198,20 +198,29 @@ public TranslatedExpression Build(CallInstruction inst) if (settings.TupleTypes && TupleTransform.MatchTupleConstruction(inst as NewObj, out var tupleElements) && tupleElements.Length >= 2) { var elementTypes = TupleType.GetTupleElementTypes(inst.Method.DeclaringType); - Debug.Assert(!elementTypes.IsDefault, "MatchTupleConstruction should not success unless we got a valid tuple type."); + var elementNames = typeHint is TupleType tt ? tt.ElementNames : default; + Debug.Assert(!elementTypes.IsDefault, "MatchTupleConstruction should not succeed unless we got a valid tuple type."); Debug.Assert(elementTypes.Length == tupleElements.Length); var tuple = new TupleExpression(); var elementRRs = new List(); - foreach (var (element, elementType) in tupleElements.Zip(elementTypes)) + foreach (var (index, element, elementType) in tupleElements.ZipWithIndex(elementTypes)) { var translatedElement = expressionBuilder.Translate(element, elementType) .ConvertTo(elementType, expressionBuilder, allowImplicitConversion: true); - tuple.Elements.Add(translatedElement.Expression); + if (elementNames.IsDefaultOrEmpty || elementNames.ElementAtOrDefault(index) is not string { Length: > 0 } name) + { + tuple.Elements.Add(translatedElement.Expression); + } + else + { + tuple.Elements.Add(new NamedArgumentExpression(name, translatedElement.Expression)); + } elementRRs.Add(translatedElement.ResolveResult); } return tuple.WithRR(new TupleResolveResult( expressionBuilder.compilation, elementRRs.ToImmutableArray(), + elementNames, valueTupleAssembly: inst.Method.DeclaringType.GetDefinition()?.ParentModule )).WithILInstruction(inst); } diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index d0cdde6def..0a3dddb155 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -22,7 +22,6 @@ using System.Diagnostics; using System.Linq; using System.Reflection.Metadata; -using System.Runtime.CompilerServices; using System.Threading; using ICSharpCode.Decompiler.CSharp.Resolver; @@ -438,7 +437,7 @@ protected internal override TranslatedExpression VisitNewObj(NewObj inst, Transl return TranslateStackAllocInitializer(b, type.TypeArguments[0]); } } - return new CallBuilder(this, typeSystem, settings).Build(inst); + return new CallBuilder(this, typeSystem, settings).Build(inst, context.TypeHint); } protected internal override TranslatedExpression VisitLdVirtDelegate(LdVirtDelegate inst, TranslationContext context) diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index 61471cb35e..28bccd9526 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -395,8 +395,14 @@ protected internal override TranslatedStatement VisitLeave(Leave inst) return new YieldBreakStatement().WithILInstruction(inst); else if (!inst.Value.MatchNop()) { + bool isLambdaOrExprTree = currentFunction.Kind is ILFunctionKind.ExpressionTree or ILFunctionKind.Delegate; var expr = exprBuilder.Translate(inst.Value, typeHint: currentResultType) .ConvertTo(currentResultType, exprBuilder, allowImplicitConversion: true); + if (isLambdaOrExprTree && IsPossibleLossOfTypeInformation(expr.Type, currentResultType)) + { + expr = new CastExpression(exprBuilder.ConvertType(currentResultType), expr) + .WithRR(new ConversionResolveResult(currentResultType, expr.ResolveResult, Conversion.IdentityConversion)).WithoutILInstruction(); + } return new ReturnStatement(expr).WithILInstruction(inst); } else @@ -419,6 +425,19 @@ protected internal override TranslatedStatement VisitLeave(Leave inst) return new GotoStatement(label).WithILInstruction(inst); } + private bool IsPossibleLossOfTypeInformation(IType givenType, IType expectedType) + { + if (NormalizeTypeVisitor.IgnoreNullability.EquivalentTypes(givenType, expectedType)) + return false; + if (expectedType is TupleType { ElementNames.IsEmpty: false }) + return true; + if (expectedType == SpecialType.Dynamic) + return true; + if (givenType == SpecialType.NullType) + return true; + return false; + } + protected internal override TranslatedStatement VisitThrow(Throw inst) { return new ThrowStatement(exprBuilder.Translate(inst.Argument)).WithILInstruction(inst); diff --git a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs index 1350d21d3b..ad864b5acd 100644 --- a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs @@ -272,11 +272,20 @@ public TranslatedExpression ConvertTo(IType targetType, ExpressionBuilder expres // Conversion of a tuple literal: convert element-wise var newTupleExpr = new TupleExpression(); var newElementRRs = new List(); - foreach (var (elementExpr, elementTargetType) in tupleExpr.Elements.Zip(targetTupleType.ElementTypes)) + // element names: discard existing names and use targetTupleType instead + var newElementNames = targetTupleType.ElementNames; + foreach (var (index, elementExpr, elementTargetType) in tupleExpr.Elements.ZipWithIndex(targetTupleType.ElementTypes)) { - var newElementExpr = new TranslatedExpression(elementExpr.Detach()) + var newElementExpr = new TranslatedExpression((elementExpr is NamedArgumentExpression nae ? nae.Expression : elementExpr).Detach()) .ConvertTo(elementTargetType, expressionBuilder, checkForOverflow, allowImplicitConversion); - newTupleExpr.Elements.Add(newElementExpr.Expression); + if (newElementNames.IsDefaultOrEmpty || newElementNames.ElementAtOrDefault(index) is not string { Length: > 0 } name) + { + newTupleExpr.Elements.Add(newElementExpr.Expression); + } + else + { + newTupleExpr.Elements.Add(new NamedArgumentExpression(name, newElementExpr.Expression)); + } newElementRRs.Add(newElementExpr.ResolveResult); } return newTupleExpr.WithILInstruction(this.ILInstructions) diff --git a/ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs b/ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs index 8f097f6832..4d140715a7 100644 --- a/ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs +++ b/ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs @@ -50,6 +50,17 @@ sealed class NormalizeTypeVisitor : TypeVisitor RemoveNullability = true, }; + internal static readonly NormalizeTypeVisitor IgnoreNullability = new NormalizeTypeVisitor { + ReplaceClassTypeParametersWithDummy = false, + ReplaceMethodTypeParametersWithDummy = false, + DynamicAndObject = false, + IntPtrToNInt = false, + TupleToUnderlyingType = false, + RemoveModOpt = true, + RemoveModReq = true, + RemoveNullability = true, + }; + public bool EquivalentTypes(IType a, IType b) { a = a.AcceptVisitor(this); diff --git a/ICSharpCode.Decompiler/Util/CollectionExtensions.cs b/ICSharpCode.Decompiler/Util/CollectionExtensions.cs index 11be785b9c..2912707ec7 100644 --- a/ICSharpCode.Decompiler/Util/CollectionExtensions.cs +++ b/ICSharpCode.Decompiler/Util/CollectionExtensions.cs @@ -21,6 +21,12 @@ public static void Deconstruct(this KeyValuePair pair, out K key, ou } #endif + public static IEnumerable<(int, A, B)> ZipWithIndex(this IEnumerable input1, IEnumerable input2) + { + int index = 0; + return input1.Zip(input2, (a, b) => (index++, a, b)); + } + public static IEnumerable<(A?, B?)> ZipLongest(this IEnumerable input1, IEnumerable input2) { using (var it1 = input1.GetEnumerator())