diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs index 5041dbcc91..a7cfc0d428 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs @@ -92,5 +92,43 @@ public static void CapturedBoolResult(Dictionary d, int key) Console.WriteLine(value2); Console.WriteLine(value2); } + + private static void GetTwo(out int a, out int b) + { + a = 1; + b = 2; + } + + public static void SameVariableUsedForTwoOutParameters() + { + // The declaration must use the explicit type: referencing an implicitly-typed + // out variable in another argument of the declaring call is an error (CS8196). + GetTwo(out int a, out a); + } + + public static int SameVariableUsedForTwoOutParametersAndRead() + { + GetTwo(out int a, out a); + return a; + } + + private static void OutAndValue(out int a, int b) + { + a = b; + } + + private static int UseAndReturn(out int a) + { + a = 1; + return a; + } + + public static void SameVariableUsedInNestedCallArgument() + { + // CS8196 also applies when the second reference is nested within + // another argument of the declaring call. + OutAndValue(out int a, UseAndReturn(out a)); + Console.WriteLine(a); + } } } diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs index 8fbc4b9a32..fe3c2e70eb 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs @@ -651,7 +651,8 @@ void InsertVariableDeclarations(TransformContext context) type = new SimpleType("var"); isOutVar = true; } - else if (dirExpr.Annotation() != null) + else if (dirExpr.Annotation() != null + && !IsReferencedWithinDeclaringCall(dirExpr, v)) { type = new SimpleType("var"); isOutVar = true; @@ -788,6 +789,45 @@ void InsertVariableDeclarations(TransformContext context) } } + /// + /// Gets whether the variable declared by is referenced again within + /// another argument of the call containing the declaration. In that case the declaration must use + /// the explicit type: referencing an implicitly-typed out variable is not permitted until overload + /// resolution of the declaring call has inferred its type (CS8196). + /// + bool IsReferencedWithinDeclaringCall(DirectionExpression dirExpr, VariableToDeclare v) + { + AstNode? call = dirExpr.Parent; + if (call == null) + return false; + for (AstNode? argument = call.FirstChild; argument != null; argument = argument.NextSibling) + { + if (argument == dirExpr) + continue; + foreach (AstNode node in argument.DescendantsAndSelf) + { + if (node is IdentifierExpression identifier && ResolveVariableToDeclare(identifier.GetILVariable()) == v) + return true; + } + } + return false; + } + + /// + /// Maps an ILVariable to the variable declaration it will end up in, following merges + /// performed by ResolveCollisions. + /// + VariableToDeclare? ResolveVariableToDeclare(ILVariable? variable) + { + if (variable == null || !variableDict.TryGetValue(variable, out VariableToDeclare? v)) + return null; + while (v.ReplacementDueToCollision is { } replacement) + { + v = replacement; + } + return v; + } + private bool CanBeDeclaredAsOutVariable(VariableToDeclare v, [NotNullWhen(true)] out DirectionExpression? dirExpr) { dirExpr = v.FirstUse.Parent as DirectionExpression;