diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Discards.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Discards.cs index 270ee15ff..dbcfcc9c8 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Discards.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Discards.cs @@ -14,6 +14,16 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty value = 0; } + public void GetOutOverloaded(out int value) + { + value = 0; + } + + public void GetOutOverloaded(out string value) + { + value = "Hello World"; + } + public void MakeValue(Func func) { @@ -28,16 +38,21 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { } - // out _ is currently not supported: the test cases below are not useful - //public void ParameterHiddenByLocal(@_ _) - //{ - // GetOut(out int value); - //} + public void ParameterHiddenByLocal(@_ _) + { + GetOut(out var _); + } - //public void DiscardedOutVsLambdaParameter() - //{ - // GetOut(out int value); - // MakeValue((@_ _) => 5); - //} + public void DiscardedOutVsLambdaParameter() + { + GetOut(out var _); + MakeValue((@_ _) => 5); + } + + public void ExplicitlyTypedDiscard() + { + GetOutOverloaded(out string _); + GetOutOverloaded(out int _); + } } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs index 4ee345c28..586adacc7 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs @@ -82,7 +82,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty private static void RefCallSiteTests() { #if CS70 - CallWithOut(out dynamic d); + CallWithOut(out var d); CallWithIn(in d); #else dynamic d; diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs index 48bc860bc..f59a17e83 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs @@ -1040,7 +1040,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { await Task.Delay(100); #if CS70 - if (string.IsNullOrEmpty(str) && int.TryParse(str, out int id)) + if (string.IsNullOrEmpty(str) && int.TryParse(str, out var id)) { #else int id; diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs index a1bdf6971..cb884bf78 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs @@ -25,7 +25,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { public static void OutVarInShortCircuit(Dictionary d) { - if (d.Count > 2 && d.TryGetValue(42, out string value)) + if (d.Count > 2 && d.TryGetValue(42, out var value)) { Console.WriteLine(value); } @@ -35,7 +35,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { // Note: needs reasoning about "definitely assigned if true" // to ensure that the value is initialized when the delegate is declared. - if (d.Count > 2 && d.TryGetValue(42, out string value)) + if (d.Count > 2 && d.TryGetValue(42, out var value)) { return delegate { Console.WriteLine(value); diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ThrowExpressions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ThrowExpressions.cs index ca248165e..fc0269c2d 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ThrowExpressions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ThrowExpressions.cs @@ -32,7 +32,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty private static int? GetIntOrNull(string v) { - if (int.TryParse(v, out int result)) + if (int.TryParse(v, out var result)) { return result; } diff --git a/ICSharpCode.Decompiler/CSharp/Annotations.cs b/ICSharpCode.Decompiler/CSharp/Annotations.cs index 5f5a2e8ba..30f221db1 100644 --- a/ICSharpCode.Decompiler/CSharp/Annotations.cs +++ b/ICSharpCode.Decompiler/CSharp/Annotations.cs @@ -345,4 +345,12 @@ namespace ICSharpCode.Decompiler.CSharp this.EqualsLambda = equals; } } + + /// + /// Annotates an out DirectionExpression if the out variable can be declared implicitly typed. + /// + public class UseImplicitlyTypedOutAnnotation + { + public static readonly UseImplicitlyTypedOutAnnotation Instance = new UseImplicitlyTypedOutAnnotation(); + } } diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index 6af7fa256..625e3be01 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -52,6 +52,7 @@ namespace ICSharpCode.Decompiler.CSharp public IReadOnlyList ArgumentToParameterMap; public bool AddNamesToPrimitiveValues; + public bool UseImplicitlyTypedOut; public bool IsExpandedForm; public int Length => Arguments.Length; @@ -62,9 +63,33 @@ namespace ICSharpCode.Decompiler.CSharp return FirstOptionalArgumentIndex; } - public IEnumerable GetArgumentResolveResults(int skipCount = 0) + public IList GetArgumentResolveResults(int skipCount = 0) { - return Arguments.Skip(skipCount).Take(GetActualArgumentCount()).Select(a => a.ResolveResult); + var expectedParameters = ExpectedParameters; + var useImplicitlyTypedOut = UseImplicitlyTypedOut; + + return Arguments + .SelectWithIndex(GetResolveResult) + .Skip(skipCount) + .Take(GetActualArgumentCount()) + .ToArray(); + + ResolveResult GetResolveResult(int index, TranslatedExpression expression) + { + var param = expectedParameters[index]; + if (useImplicitlyTypedOut && param.IsOut) + return OutVarResolveResult.Instance; + return expression.ResolveResult; + } + } + + public IList GetArgumentResolveResultsDirect(int skipCount = 0) + { + return Arguments + .Skip(skipCount) + .Take(GetActualArgumentCount()) + .Select(a => a.ResolveResult) + .ToArray(); } public IEnumerable GetArgumentExpressions(int skipCount = 0) @@ -87,9 +112,10 @@ namespace ICSharpCode.Decompiler.CSharp } } int argumentCount = GetActualArgumentCount(); + var useImplicitlyTypedOut = UseImplicitlyTypedOut; if (ArgumentNames == null) { - return Arguments.Skip(skipCount).Take(argumentCount).Select(arg => arg.Expression); + return Arguments.Skip(skipCount).Take(argumentCount).Select(arg => AddAnnotations(arg.Expression)); } else { @@ -97,11 +123,23 @@ namespace ICSharpCode.Decompiler.CSharp return Arguments.Take(argumentCount).Zip(ArgumentNames.Take(argumentCount), (arg, name) => { if (name == null) - return arg.Expression; + return AddAnnotations(arg.Expression); else - return new NamedArgumentExpression(name, arg); + return new NamedArgumentExpression(name, AddAnnotations(arg.Expression)); }); } + + Expression AddAnnotations(Expression expression) + { + if (!useImplicitlyTypedOut) + return expression; + if (expression.GetResolveResult() is ByReferenceResolveResult brrr + && brrr.IsOut) + { + expression.AddAnnotation(UseImplicitlyTypedOutAnnotation.Instance); + } + return expression; + } } public bool CanInferAnonymousTypePropertyNamesFromArguments() @@ -255,13 +293,14 @@ namespace ICSharpCode.Decompiler.CSharp { return new InvocationExpression(target, argumentList.GetArgumentExpressions()) .WithRR(new CSharpInvocationResolveResult(target.ResolveResult, method, - argumentList.GetArgumentResolveResults().ToList(), isExpandedForm: argumentList.IsExpandedForm)); + argumentList.GetArgumentResolveResults(), isExpandedForm: argumentList.IsExpandedForm)); } if (method is VarArgInstanceMethod) { argumentList.FirstOptionalArgumentIndex = -1; argumentList.AddNamesToPrimitiveValues = false; + argumentList.UseImplicitlyTypedOut = false; int regularParameterCount = ((VarArgInstanceMethod)method).RegularParameterCount; var argListArg = new UndocumentedExpression(); argListArg.UndocumentedExpressionType = UndocumentedExpressionType.ArgList; @@ -293,7 +332,7 @@ namespace ICSharpCode.Decompiler.CSharp { return new InvocationExpression(target, argumentList.GetArgumentExpressions()) .WithRR(new CSharpInvocationResolveResult(target.ResolveResult, method, - argumentList.GetArgumentResolveResults().ToList(), isExpandedForm: argumentList.IsExpandedForm, isDelegateInvocation: true)); + argumentList.GetArgumentResolveResults(), isExpandedForm: argumentList.IsExpandedForm, isDelegateInvocation: true)); } if (settings.StringInterpolation && IsInterpolatedStringCreation(method, argumentList) && @@ -340,7 +379,7 @@ namespace ICSharpCode.Decompiler.CSharp } var formattableStringType = expressionBuilder.compilation.FindType(KnownTypeCode.FormattableString); var isrr = new InterpolatedStringResolveResult(expressionBuilder.compilation.FindType(KnownTypeCode.String), - format, argumentList.GetArgumentResolveResults().Skip(1).ToArray()); + format, argumentList.GetArgumentResolveResults(1).ToArray()); var expr = new InterpolatedStringExpression(); expr.Content.AddRange(content); if (method.Name == "Format") @@ -363,7 +402,7 @@ namespace ICSharpCode.Decompiler.CSharp argumentList.CheckNoNamedOrOptionalArguments(); return HandleDelegateEqualityComparison(method, argumentList.Arguments) .WithRR(new CSharpInvocationResolveResult(target.ResolveResult, method, - argumentList.GetArgumentResolveResults().ToList(), isExpandedForm: argumentList.IsExpandedForm)); + argumentList.GetArgumentResolveResults(), isExpandedForm: argumentList.IsExpandedForm)); } if (method.IsOperator && method.Name == "op_Implicit" && argumentList.Length == 1) @@ -420,7 +459,7 @@ namespace ICSharpCode.Decompiler.CSharp typeArgumentList.AddRange(method.TypeArguments.Select(expressionBuilder.ConvertType)); return new InvocationExpression(targetExpr, argumentList.GetArgumentExpressions()) .WithRR(new CSharpInvocationResolveResult(target.ResolveResult, foundMethod, - argumentList.GetArgumentResolveResults().ToList(), isExpandedForm: argumentList.IsExpandedForm)); + argumentList.GetArgumentResolveResultsDirect(), isExpandedForm: argumentList.IsExpandedForm)); } /// @@ -457,6 +496,7 @@ namespace ICSharpCode.Decompiler.CSharp firstParamIndex: 0, args, null); argumentList.ArgumentNames = null; argumentList.AddNamesToPrimitiveValues = false; + argumentList.UseImplicitlyTypedOut = false; var transform = GetRequiredTransformationsForCall(expectedTargetDetails, method, ref unused, ref argumentList, CallTransformation.None, out _); Debug.Assert(transform == CallTransformation.None || transform == CallTransformation.NoOptionalArgumentAllowed); @@ -755,6 +795,7 @@ namespace ICSharpCode.Decompiler.CSharp list.IsExpandedForm = isExpandedForm; list.IsPrimitiveValue = isPrimitiveValue; list.FirstOptionalArgumentIndex = firstOptionalArgumentIndex; + list.UseImplicitlyTypedOut = true; list.AddNamesToPrimitiveValues = expressionBuilder.settings.NamedArguments && expressionBuilder.settings.NonTrailingNamedArguments; return list; } @@ -787,7 +828,8 @@ namespace ICSharpCode.Decompiler.CSharp } } if (IsUnambiguousCall(expectedTargetDetails, method, targetResolveResult, Empty.Array, - expandedArguments, argumentNames: null, firstOptionalArgumentIndex: -1, out _, + expandedArguments.SelectArray(a => a.ResolveResult), argumentNames: null, + firstOptionalArgumentIndex: -1, out _, out var bestCandidateIsExpandedForm) == OverloadResolutionErrors.None && bestCandidateIsExpandedForm) { expectedParameters = expandedParameters; @@ -917,7 +959,7 @@ namespace ICSharpCode.Decompiler.CSharp bool argumentsCasted = false; OverloadResolutionErrors errors; while ((errors = IsUnambiguousCall(expectedTargetDetails, method, targetResolveResult, typeArguments, - argumentList.Arguments, argumentList.ArgumentNames, argumentList.FirstOptionalArgumentIndex, out foundMethod, + argumentList.GetArgumentResolveResults().ToArray(), argumentList.ArgumentNames, argumentList.FirstOptionalArgumentIndex, out foundMethod, out var bestCandidateIsExpandedForm)) != OverloadResolutionErrors.None || bestCandidateIsExpandedForm != argumentList.IsExpandedForm) { switch (errors) @@ -958,6 +1000,7 @@ namespace ICSharpCode.Decompiler.CSharp appliedRequireTypeArgumentsShortcut = false; } argumentsCasted = true; + argumentList.UseImplicitlyTypedOut = false; CastArguments(argumentList.Arguments, argumentList.ExpectedParameters); } else if ((allowedTransforms & CallTransformation.RequireTarget) != 0 && !requireTarget) @@ -1116,27 +1159,32 @@ namespace ICSharpCode.Decompiler.CSharp } OverloadResolutionErrors IsUnambiguousCall(ExpectedTargetDetails expectedTargetDetails, IMethod method, - ResolveResult target, IType[] typeArguments, IList arguments, + ResolveResult target, IType[] typeArguments, ResolveResult[] arguments, string[] argumentNames, int firstOptionalArgumentIndex, out IParameterizedMember foundMember, out bool bestCandidateIsExpandedForm) { foundMember = null; bestCandidateIsExpandedForm = false; - var lookup = new MemberLookup(resolver.CurrentTypeDefinition, resolver.CurrentTypeDefinition.ParentModule); + var currentTypeDefinition = resolver.CurrentTypeDefinition; + var lookup = new MemberLookup(currentTypeDefinition, currentTypeDefinition.ParentModule); Log.WriteLine("IsUnambiguousCall: Performing overload resolution for " + method); - Log.WriteCollection(" Arguments: ", arguments.Select(a => a.ResolveResult)); + Log.WriteCollection(" Arguments: ", arguments); + + argumentNames = firstOptionalArgumentIndex < 0 || argumentNames == null + ? argumentNames + : argumentNames.Take(firstOptionalArgumentIndex).ToArray(); var or = new OverloadResolution(resolver.Compilation, - firstOptionalArgumentIndex < 0 ? arguments.SelectArray(a => a.ResolveResult) : arguments.Take(firstOptionalArgumentIndex).Select(a => a.ResolveResult).ToArray(), - argumentNames: firstOptionalArgumentIndex < 0 || argumentNames == null ? argumentNames : argumentNames.Take(firstOptionalArgumentIndex).ToArray(), - typeArguments: typeArguments, + arguments, argumentNames, typeArguments, conversions: expressionBuilder.resolver.conversions); if (expectedTargetDetails.CallOpCode == OpCode.NewObj) { foreach (IMethod ctor in method.DeclaringType.GetConstructors()) { - if (lookup.IsAccessible(ctor, allowProtectedAccess: resolver.CurrentTypeDefinition == method.DeclaringTypeDefinition)) + bool allowProtectedAccess = + resolver.CurrentTypeDefinition == method.DeclaringTypeDefinition; + if (lookup.IsAccessible(ctor, allowProtectedAccess)) { or.AddCandidate(ctor); } @@ -1145,12 +1193,12 @@ namespace ICSharpCode.Decompiler.CSharp else if (method.IsOperator) { IEnumerable operatorCandidates; - if (arguments.Count == 1) + if (arguments.Length == 1) { IType argType = NullableType.GetUnderlyingType(arguments[0].Type); operatorCandidates = resolver.GetUserDefinedOperatorCandidates(argType, method.Name); } - else if (arguments.Count == 2) + else if (arguments.Length == 2) { IType lhsType = NullableType.GetUnderlyingType(arguments[0].Type); IType rhsType = NullableType.GetUnderlyingType(arguments[1].Type); @@ -1170,7 +1218,8 @@ namespace ICSharpCode.Decompiler.CSharp } else if (target == null) { - var result = resolver.ResolveSimpleName(method.Name, typeArguments, isInvocationTarget: true) as MethodGroupResolveResult; + var result = resolver.ResolveSimpleName(method.Name, typeArguments, isInvocationTarget: true) + as MethodGroupResolveResult; if (result == null) return OverloadResolutionErrors.AmbiguousMatch; or.AddMethodLists(result.MethodsGroupedByDeclaringType.ToArray()); @@ -1386,13 +1435,14 @@ namespace ICSharpCode.Decompiler.CSharp } } return atce.WithRR(new CSharpInvocationResolveResult( - target, method, argumentList.GetArgumentResolveResults().ToList(), + target, method, argumentList.GetArgumentResolveResults(), isExpandedForm: argumentList.IsExpandedForm, argumentToParameterMap: argumentList.ArgumentToParameterMap )); } else { - while (IsUnambiguousCall(expectedTargetDetails, method, null, Empty.Array, argumentList.Arguments, + while (IsUnambiguousCall(expectedTargetDetails, method, null, Empty.Array, + argumentList.GetArgumentResolveResults().ToArray(), argumentList.ArgumentNames, argumentList.FirstOptionalArgumentIndex, out _, out var bestCandidateIsExpandedForm) != OverloadResolutionErrors.None || bestCandidateIsExpandedForm != argumentList.IsExpandedForm) { diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs index ebd436c06..71ab425bb 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs @@ -580,17 +580,36 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } else if (CanBeDeclaredAsOutVariable(v, out var dirExpr)) { - // 'T v; SomeCall(out v);' can be combined to 'SomeCall(out var v);' + // 'T v; SomeCall(out v);' can be combined to 'SomeCall(out T v);' AstType type; if (context.Settings.AnonymousTypes && v.Type.ContainsAnonymousType()) { type = new SimpleType("var"); } + else if (dirExpr.Annotation() != null) + { + type = new SimpleType("var"); + } else { type = context.TypeSystemAstBuilder.ConvertType(v.Type); } - var ovd = new OutVarDeclarationExpression(type, v.Name); + string name; + // Variable is not used and discards are allowed, we can simplify this to 'out T _'. + // TODO: if no variable named _ is declared and var is used instead of T, use out _. + // Note: ExpressionBuilder.HidesVariableWithName produces inaccurate results, because it + // does not take lambdas and local functions into account, that are defined in the same + // scope as v. + if (context.Settings.Discards && v.ILVariable.LoadCount == 0 + && v.ILVariable.StoreCount == 0 && v.ILVariable.AddressCount == 1) + { + name = "_"; + } + else + { + name = v.Name; + } + var ovd = new OutVarDeclarationExpression(type, name); ovd.Variable.AddAnnotation(new ILVariableResolveResult(ilVariable)); ovd.CopyAnnotationsFrom(dirExpr); replacements.Add((dirExpr, ovd)); diff --git a/ICSharpCode.Decompiler/Semantics/OutVarResolveResult.cs b/ICSharpCode.Decompiler/Semantics/OutVarResolveResult.cs index 9d9674f6a..214c994b5 100644 --- a/ICSharpCode.Decompiler/Semantics/OutVarResolveResult.cs +++ b/ICSharpCode.Decompiler/Semantics/OutVarResolveResult.cs @@ -26,6 +26,8 @@ namespace ICSharpCode.Decompiler.Semantics /// class OutVarResolveResult : ResolveResult { + public static readonly OutVarResolveResult Instance = new OutVarResolveResult(); + public OutVarResolveResult() : base(SpecialType.NoType) { } } }