Browse Source

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
pull/3878/head
Siegfried Pammer 2 months ago committed by Siegfried Pammer
parent
commit
1e6a19e2c4
  1. 19
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs
  2. 42
      ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs

19
ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs

@ -92,5 +92,24 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -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;
}
}
}

42
ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs

@ -651,7 +651,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -651,7 +651,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
type = new SimpleType("var");
isOutVar = true;
}
else if (dirExpr.Annotation<UseImplicitlyTypedOutAnnotation>() != null)
else if (dirExpr.Annotation<UseImplicitlyTypedOutAnnotation>() != null
&& !IsReferencedWithinDeclaringCall(dirExpr, v))
{
type = new SimpleType("var");
isOutVar = true;
@ -788,6 +789,45 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -788,6 +789,45 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
}
}
/// <summary>
/// Gets whether the variable declared by <paramref name="dirExpr"/> 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).
/// </summary>
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;
}
/// <summary>
/// Maps an ILVariable to the variable declaration it will end up in, following merges
/// performed by ResolveCollisions.
/// </summary>
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;

Loading…
Cancel
Save