diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs index 5041dbcc9..45cb081ab 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs @@ -92,5 +92,24 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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; + } } } diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs index 8fbc4b9a3..fe3c2e70e 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs @@ -651,7 +651,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms 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 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } } + /// + /// 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;