From 1e6a19e2c41513dacff435b8e4d25985da14a970 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 5 Jul 2026 20:57:56 +0200 Subject: [PATCH] Fix #3860: Avoid 'out var' if the variable recurs in the argument list Passing the same local as multiple out arguments of one call made DeclareVariables turn the first use into an implicitly-typed declaration, producing 'f(out var x, out x)'. Referencing an implicitly-typed out variable in another argument of the declaring call is rejected by the compiler (CS8196), because its type is only inferred once overload resolution of that call has completed. The explicitly-typed form 'f(out int x, out x)' is valid, so fall back to the explicit type in that case. Assisted-by: Claude:claude-fable-5:Claude Code --- .../TestCases/Pretty/OutVariables.cs | 19 +++++++++ .../CSharp/Transforms/DeclareVariables.cs | 42 ++++++++++++++++++- 2 files changed, 60 insertions(+), 1 deletion(-) 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;