Skip to content

Commit

Permalink
Fix #3014: Missing type information in lambda expressions.
Browse files Browse the repository at this point in the history
  • Loading branch information
siegfriedpammer committed Nov 1, 2023
1 parent ce891da commit eae54dd
Show file tree
Hide file tree
Showing 9 changed files with 92 additions and 18 deletions.
12 changes: 6 additions & 6 deletions ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs
Original file line number Diff line number Diff line change
Expand Up @@ -223,15 +223,15 @@ public SimpleTypeWithMultipleCtors(int i)
private dynamic ViewBag;

public static readonly object[] SupportedMethods = new object[2] {
ToCode(null, () => ((IQueryable<object>)null).Aggregate((object o1, object o2) => null)),
ToCode(null, () => ((IEnumerable<object>)null).Aggregate((object o1, object o2) => null))
ToCode(null, () => ((IQueryable<object>)null).Aggregate((object o1, object o2) => (object)null)),
ToCode(null, () => ((IEnumerable<object>)null).Aggregate((object o1, object o2) => (object)null))
};

public static readonly object[] SupportedMethods2 = new object[4] {
ToCode(null, () => ((IQueryable<object>)null).Aggregate(null, (object o1, object o2) => null)),
ToCode(null, () => ((IQueryable<object>)null).Aggregate((object)null, (Expression<Func<object, object, object>>)((object o1, object o2) => null), (Expression<Func<object, object>>)((object o) => null))),
ToCode(null, () => ((IEnumerable<object>)null).Aggregate(null, (object o1, object o2) => null)),
ToCode(null, () => ((IEnumerable<object>)null).Aggregate((object)null, (Func<object, object, object>)((object o1, object o2) => null), (Func<object, object>)((object o) => null)))
ToCode(null, () => ((IQueryable<object>)null).Aggregate(null, (object o1, object o2) => (object)null)),
ToCode(null, () => ((IQueryable<object>)null).Aggregate(null, (object o1, object o2) => (object)null, (object o) => (object)null)),
ToCode(null, () => ((IEnumerable<object>)null).Aggregate(null, (object o1, object o2) => (object)null)),
ToCode(null, () => ((IEnumerable<object>)null).Aggregate(null, (object o1, object o2) => (object)null, (object o) => (object)null))
};

public static void TestCall(object a)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public class Issue1660 : Issue1660Base
public Action<object> M(object state)
{
return delegate (object x) {
base.BaseCall(x, state, (Func<object>)(() => null));
base.BaseCall(x, state, () => (object)null);
};
}
}
Expand Down
25 changes: 23 additions & 2 deletions ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public struct GenericStruct<T>
public int AccessPartiallyNamed => PartiallyNamed.a + PartiallyNamed.Item3;

public ValueTuple<int> NewTuple1 => new ValueTuple<int>(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);
Expand Down Expand Up @@ -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)
Expand All @@ -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());
Expand Down
17 changes: 13 additions & 4 deletions ICSharpCode.Decompiler/CSharp/CallBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 _))
{
Expand All @@ -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<ResolveResult>();
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);
}
Expand Down
3 changes: 1 addition & 2 deletions ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down
19 changes: 19 additions & 0 deletions ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Expand Down
15 changes: 12 additions & 3 deletions ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ResolveResult>();
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)
Expand Down
11 changes: 11 additions & 0 deletions ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
6 changes: 6 additions & 0 deletions ICSharpCode.Decompiler/Util/CollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ public static void Deconstruct<K, V>(this KeyValuePair<K, V> pair, out K key, ou
}
#endif

public static IEnumerable<(int, A, B)> ZipWithIndex<A, B>(this IEnumerable<A> input1, IEnumerable<B> input2)
{
int index = 0;
return input1.Zip(input2, (a, b) => (index++, a, b));
}

public static IEnumerable<(A?, B?)> ZipLongest<A, B>(this IEnumerable<A> input1, IEnumerable<B> input2)
{
using (var it1 = input1.GetEnumerator())
Expand Down

0 comments on commit eae54dd

Please sign in to comment.