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. 17
      ICSharpCode.Decompiler/CSharp/CallBuilder.cs
  5. 3
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  6. 19
      ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
  7. 15
      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
private dynamic ViewBag; private dynamic ViewBag;
public static readonly object[] SupportedMethods = new object[2] { public static readonly object[] SupportedMethods = new object[2] {
ToCode(null, () => ((IQueryable<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) => null)) ToCode(null, () => ((IEnumerable<object>)null).Aggregate((object o1, object o2) => (object)null))
}; };
public static readonly object[] SupportedMethods2 = new object[4] { 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(null, (object o1, object o2) => (object)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, () => ((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) => null)), ToCode(null, () => ((IEnumerable<object>)null).Aggregate(null, (object o1, object o2) => (object)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, () => ((IEnumerable<object>)null).Aggregate(null, (object o1, object o2) => (object)null, (object o) => (object)null))
}; };
public static void TestCall(object a) public static void TestCall(object a)

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

@ -120,7 +120,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.ILPretty
public Action<object> M(object state) public Action<object> M(object state)
{ {
return delegate (object x) { 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
public int AccessPartiallyNamed => PartiallyNamed.a + PartiallyNamed.Item3; public int AccessPartiallyNamed => PartiallyNamed.a + PartiallyNamed.Item3;
public ValueTuple<int> NewTuple1 => new ValueTuple<int>(1); 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 object BoxedTuple10 => (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
public (uint, int) SwapUnnamed => (Unnamed2.Item2, Unnamed2.Item1); public (uint, int) SwapUnnamed => (Unnamed2.Item2, Unnamed2.Item1);
@ -115,7 +115,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public void NamedTupleOut(out (int A, string B, Action C, dynamic D) 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) public void NamedTupleIn(in (int A, string B, Action C, dynamic D) tuple)
@ -141,6 +141,27 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
Console.WriteLine(TupleDict.Values.ToList().First().d); 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() public void Issue1174()
{ {
Console.WriteLine((1, 2, 3).GetHashCode()); Console.WriteLine((1, 2, 3).GetHashCode());

17
ICSharpCode.Decompiler/CSharp/CallBuilder.cs

@ -189,7 +189,7 @@ namespace ICSharpCode.Decompiler.CSharp
this.typeSystem = typeSystem; 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 _)) if (inst is NewObj newobj && IL.Transforms.DelegateConstruction.MatchDelegateConstruction(newobj, out _, out _, out _))
{ {
@ -198,20 +198,29 @@ namespace ICSharpCode.Decompiler.CSharp
if (settings.TupleTypes && TupleTransform.MatchTupleConstruction(inst as NewObj, out var tupleElements) && tupleElements.Length >= 2) if (settings.TupleTypes && TupleTransform.MatchTupleConstruction(inst as NewObj, out var tupleElements) && tupleElements.Length >= 2)
{ {
var elementTypes = TupleType.GetTupleElementTypes(inst.Method.DeclaringType); 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); Debug.Assert(elementTypes.Length == tupleElements.Length);
var tuple = new TupleExpression(); var tuple = new TupleExpression();
var elementRRs = new List<ResolveResult>(); 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) var translatedElement = expressionBuilder.Translate(element, elementType)
.ConvertTo(elementType, expressionBuilder, allowImplicitConversion: true); .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); elementRRs.Add(translatedElement.ResolveResult);
} }
return tuple.WithRR(new TupleResolveResult( return tuple.WithRR(new TupleResolveResult(
expressionBuilder.compilation, expressionBuilder.compilation,
elementRRs.ToImmutableArray(), elementRRs.ToImmutableArray(),
elementNames,
valueTupleAssembly: inst.Method.DeclaringType.GetDefinition()?.ParentModule valueTupleAssembly: inst.Method.DeclaringType.GetDefinition()?.ParentModule
)).WithILInstruction(inst); )).WithILInstruction(inst);
} }

3
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -22,7 +22,6 @@ using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reflection.Metadata; using System.Reflection.Metadata;
using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
using ICSharpCode.Decompiler.CSharp.Resolver; using ICSharpCode.Decompiler.CSharp.Resolver;
@ -438,7 +437,7 @@ namespace ICSharpCode.Decompiler.CSharp
return TranslateStackAllocInitializer(b, type.TypeArguments[0]); 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) protected internal override TranslatedExpression VisitLdVirtDelegate(LdVirtDelegate inst, TranslationContext context)

19
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -395,8 +395,14 @@ namespace ICSharpCode.Decompiler.CSharp
return new YieldBreakStatement().WithILInstruction(inst); return new YieldBreakStatement().WithILInstruction(inst);
else if (!inst.Value.MatchNop()) else if (!inst.Value.MatchNop())
{ {
bool isLambdaOrExprTree = currentFunction.Kind is ILFunctionKind.ExpressionTree or ILFunctionKind.Delegate;
var expr = exprBuilder.Translate(inst.Value, typeHint: currentResultType) var expr = exprBuilder.Translate(inst.Value, typeHint: currentResultType)
.ConvertTo(currentResultType, exprBuilder, allowImplicitConversion: true); .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); return new ReturnStatement(expr).WithILInstruction(inst);
} }
else else
@ -419,6 +425,19 @@ namespace ICSharpCode.Decompiler.CSharp
return new GotoStatement(label).WithILInstruction(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) protected internal override TranslatedStatement VisitThrow(Throw inst)
{ {
return new ThrowStatement(exprBuilder.Translate(inst.Argument)).WithILInstruction(inst); return new ThrowStatement(exprBuilder.Translate(inst.Argument)).WithILInstruction(inst);

15
ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs

@ -272,11 +272,20 @@ namespace ICSharpCode.Decompiler.CSharp
// Conversion of a tuple literal: convert element-wise // Conversion of a tuple literal: convert element-wise
var newTupleExpr = new TupleExpression(); var newTupleExpr = new TupleExpression();
var newElementRRs = new List<ResolveResult>(); 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); .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); newElementRRs.Add(newElementExpr.ResolveResult);
} }
return newTupleExpr.WithILInstruction(this.ILInstructions) return newTupleExpr.WithILInstruction(this.ILInstructions)

11
ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs

@ -50,6 +50,17 @@ namespace ICSharpCode.Decompiler.TypeSystem
RemoveNullability = true, 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) public bool EquivalentTypes(IType a, IType b)
{ {
a = a.AcceptVisitor(this); a = a.AcceptVisitor(this);

6
ICSharpCode.Decompiler/Util/CollectionExtensions.cs

@ -21,6 +21,12 @@ namespace ICSharpCode.Decompiler.Util
} }
#endif #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) public static IEnumerable<(A?, B?)> ZipLongest<A, B>(this IEnumerable<A> input1, IEnumerable<B> input2)
{ {
using (var it1 = input1.GetEnumerator()) using (var it1 = input1.GetEnumerator())

Loading…
Cancel
Save