Browse Source

Fix #3014: Missing type information in lambda expressions.

pull/3111/head
Siegfried Pammer 2 years ago
parent
commit
eae54ddf24
  1. 12
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs
  2. 2
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/FixProxyCalls.cs
  3. 25
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.cs
  4. 15
      ICSharpCode.Decompiler/CSharp/CallBuilder.cs
  5. 3
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  6. 19
      ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
  7. 13
      ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs
  8. 11
      ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs
  9. 6
      ICSharpCode.Decompiler/Util/CollectionExtensions.cs

12
ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs

@ -223,15 +223,15 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -223,15 +223,15 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
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)

2
ICSharpCode.Decompiler.Tests/TestCases/Pretty/FixProxyCalls.cs

@ -120,7 +120,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.ILPretty @@ -120,7 +120,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.ILPretty
public Action<object> M(object state)
{
return delegate (object x) {
base.BaseCall(x, state, (Func<object>)(() => null));
base.BaseCall(x, state, () => (object)null);
};
}
}

25
ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.cs

@ -77,7 +77,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -77,7 +77,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
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);
@ -115,7 +115,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -115,7 +115,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
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 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -141,6 +141,27 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
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());

15
ICSharpCode.Decompiler/CSharp/CallBuilder.cs

@ -189,7 +189,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -189,7 +189,7 @@ namespace ICSharpCode.Decompiler.CSharp
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 @@ namespace ICSharpCode.Decompiler.CSharp @@ -198,20 +198,29 @@ namespace ICSharpCode.Decompiler.CSharp
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);
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);
}

3
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -22,7 +22,6 @@ using System.Collections.Immutable; @@ -22,7 +22,6 @@ using System.Collections.Immutable;
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 @@ namespace ICSharpCode.Decompiler.CSharp @@ -438,7 +437,7 @@ namespace ICSharpCode.Decompiler.CSharp
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)

19
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -395,8 +395,14 @@ namespace ICSharpCode.Decompiler.CSharp @@ -395,8 +395,14 @@ namespace ICSharpCode.Decompiler.CSharp
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 @@ namespace ICSharpCode.Decompiler.CSharp @@ -419,6 +425,19 @@ namespace ICSharpCode.Decompiler.CSharp
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);

13
ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs

@ -272,11 +272,20 @@ namespace ICSharpCode.Decompiler.CSharp @@ -272,11 +272,20 @@ namespace ICSharpCode.Decompiler.CSharp
// 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);
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)

11
ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs

@ -50,6 +50,17 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -50,6 +50,17 @@ namespace ICSharpCode.Decompiler.TypeSystem
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);

6
ICSharpCode.Decompiler/Util/CollectionExtensions.cs

@ -21,6 +21,12 @@ namespace ICSharpCode.Decompiler.Util @@ -21,6 +21,12 @@ namespace ICSharpCode.Decompiler.Util
}
#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())

Loading…
Cancel
Save