mirror of
https://github.com/AssetRipper/ILSpy.git
synced 2026-11-13 20:05:48 -10:00
795 lines
26 KiB
C#
795 lines
26 KiB
C#
// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
|
// software and associated documentation files (the "Software"), to deal in the Software
|
|
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
|
// to whom the Software is furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in all copies or
|
|
// substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
// DEALINGS IN THE SOFTWARE.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
|
|
using ICSharpCode.Decompiler.CSharp.Syntax;
|
|
using ICSharpCode.Decompiler.IL;
|
|
using ICSharpCode.Decompiler.IL.Transforms;
|
|
using ICSharpCode.Decompiler.Semantics;
|
|
using ICSharpCode.Decompiler.TypeSystem;
|
|
using ICSharpCode.Decompiler.Util;
|
|
|
|
namespace ICSharpCode.Decompiler.CSharp.Transforms
|
|
{
|
|
/// <summary>
|
|
/// Insert variable declarations.
|
|
/// </summary>
|
|
public class DeclareVariables : IAstTransform
|
|
{
|
|
/// <summary>
|
|
/// Represents a position immediately before nextNode.
|
|
/// nextNode is either an ExpressionStatement in a BlockStatement, or an initializer in a for-loop.
|
|
/// </summary>
|
|
[DebuggerDisplay("level = {level}, nextNode = {nextNode}")]
|
|
struct InsertionPoint
|
|
{
|
|
/// <summary>
|
|
/// The nesting level of `nextNode` within the AST.
|
|
/// Used to speed up FindCommonParent().
|
|
/// </summary>
|
|
internal int level;
|
|
internal AstNode nextNode;
|
|
|
|
/// <summary>Go up one level</summary>
|
|
internal InsertionPoint Up()
|
|
{
|
|
return new InsertionPoint {
|
|
level = level - 1,
|
|
nextNode = nextNode.Parent
|
|
};
|
|
}
|
|
|
|
internal InsertionPoint UpTo(int targetLevel)
|
|
{
|
|
InsertionPoint result = this;
|
|
while (result.level > targetLevel)
|
|
{
|
|
result.nextNode = result.nextNode.Parent;
|
|
result.level -= 1;
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
enum VariableInitKind
|
|
{
|
|
None,
|
|
NeedsDefaultValue,
|
|
NeedsSkipInit
|
|
}
|
|
|
|
[DebuggerDisplay("VariableToDeclare(Name={Name})")]
|
|
class VariableToDeclare
|
|
{
|
|
public readonly ILVariable ILVariable;
|
|
public IType Type => ILVariable.Type;
|
|
public string Name => ILVariable.Name;
|
|
|
|
/// <summary>
|
|
/// Whether the variable needs to be default-initialized.
|
|
/// </summary>
|
|
public VariableInitKind DefaultInitialization;
|
|
|
|
/// <summary>
|
|
/// Integer value that can be used to compare to VariableToDeclare instances
|
|
/// to determine which variable was used first in the source code.
|
|
///
|
|
/// The variable with the lower SourceOrder value has the insertion point
|
|
/// that comes first in the source code.
|
|
/// </summary>
|
|
public int SourceOrder;
|
|
|
|
/// <summary>
|
|
/// The insertion point, i.e. the node before which the variable declaration should be inserted.
|
|
/// </summary>
|
|
public InsertionPoint InsertionPoint;
|
|
|
|
/// <summary>
|
|
/// The first use of the variable.
|
|
/// </summary>
|
|
public IdentifierExpression FirstUse;
|
|
|
|
public VariableToDeclare ReplacementDueToCollision;
|
|
public bool InvolvedInCollision;
|
|
public bool RemovedDueToCollision => ReplacementDueToCollision != null;
|
|
public bool DeclaredInDeconstruction;
|
|
|
|
public VariableToDeclare(ILVariable variable, InsertionPoint insertionPoint, IdentifierExpression firstUse, int sourceOrder)
|
|
{
|
|
this.ILVariable = variable;
|
|
if (variable.UsesInitialValue)
|
|
{
|
|
if (variable.InitialValueIsInitialized)
|
|
{
|
|
this.DefaultInitialization = VariableInitKind.NeedsDefaultValue;
|
|
}
|
|
else
|
|
{
|
|
this.DefaultInitialization = VariableInitKind.NeedsSkipInit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this.DefaultInitialization = VariableInitKind.None;
|
|
}
|
|
this.InsertionPoint = insertionPoint;
|
|
this.FirstUse = firstUse;
|
|
this.SourceOrder = sourceOrder;
|
|
}
|
|
}
|
|
|
|
readonly Dictionary<ILVariable, VariableToDeclare> variableDict = new Dictionary<ILVariable, VariableToDeclare>();
|
|
TransformContext context;
|
|
|
|
public void Run(AstNode rootNode, TransformContext context)
|
|
{
|
|
try
|
|
{
|
|
if (this.context != null)
|
|
throw new InvalidOperationException("Reentrancy in DeclareVariables?");
|
|
this.context = context;
|
|
variableDict.Clear();
|
|
EnsureExpressionStatementsAreValid(rootNode);
|
|
FindInsertionPoints(rootNode, 0);
|
|
ResolveCollisions();
|
|
InsertDeconstructionVariableDeclarations();
|
|
InsertVariableDeclarations(context);
|
|
UpdateAnnotations(rootNode);
|
|
}
|
|
finally
|
|
{
|
|
this.context = null;
|
|
variableDict.Clear();
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Analyze the input AST (containing undeclared variables)
|
|
/// for where those variables would be declared by this transform.
|
|
/// Analysis does not modify the AST.
|
|
/// </summary>
|
|
public void Analyze(AstNode rootNode)
|
|
{
|
|
variableDict.Clear();
|
|
FindInsertionPoints(rootNode, 0);
|
|
ResolveCollisions();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the position where the declaration for the variable will be inserted.
|
|
/// </summary>
|
|
public AstNode GetDeclarationPoint(ILVariable variable)
|
|
{
|
|
VariableToDeclare v = variableDict[variable];
|
|
while (v.ReplacementDueToCollision != null)
|
|
{
|
|
v = v.ReplacementDueToCollision;
|
|
}
|
|
return v.InsertionPoint.nextNode;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether a variable was merged with other variables.
|
|
/// </summary>
|
|
public bool WasMerged(ILVariable variable)
|
|
{
|
|
VariableToDeclare v = variableDict[variable];
|
|
return v.InvolvedInCollision || v.RemovedDueToCollision;
|
|
}
|
|
|
|
public void ClearAnalysisResults()
|
|
{
|
|
variableDict.Clear();
|
|
}
|
|
|
|
#region EnsureExpressionStatementsAreValid
|
|
void EnsureExpressionStatementsAreValid(AstNode rootNode)
|
|
{
|
|
foreach (var stmt in rootNode.DescendantsAndSelf.OfType<ExpressionStatement>())
|
|
{
|
|
if (stmt.Expression is DirectionExpression dir && IsValidInStatementExpression(dir.Expression))
|
|
{
|
|
stmt.Expression = dir.Expression.Detach();
|
|
}
|
|
else if (!IsValidInStatementExpression(stmt.Expression))
|
|
{
|
|
// fetch ILFunction
|
|
var function = stmt.Ancestors.SelectMany(a => a.Annotations.OfType<ILFunction>()).First(f => f.Parent == null);
|
|
// if possible use C# 7.0 discard-assignment
|
|
if (context.Settings.Discards && !ExpressionBuilder.HidesVariableWithName(function, "_"))
|
|
{
|
|
stmt.Expression = new AssignmentExpression(
|
|
new IdentifierExpression("_"), // no ResolveResult
|
|
stmt.Expression.Detach());
|
|
}
|
|
else
|
|
{
|
|
// assign result to dummy variable
|
|
var type = stmt.Expression.GetResolveResult().Type;
|
|
var v = function.RegisterVariable(
|
|
VariableKind.StackSlot,
|
|
type,
|
|
AssignVariableNames.GenerateVariableName(function, type,
|
|
stmt.Expression.Annotations.OfType<ILInstruction>()
|
|
.Where(AssignVariableNames.IsSupportedInstruction).FirstOrDefault(),
|
|
mustResolveConflicts: true)
|
|
);
|
|
stmt.Expression = new AssignmentExpression(
|
|
new IdentifierExpression(v.Name).WithRR(new ILVariableResolveResult(v, v.Type)),
|
|
stmt.Expression.Detach());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static bool IsValidInStatementExpression(Expression expr)
|
|
{
|
|
switch (expr)
|
|
{
|
|
case InvocationExpression _:
|
|
case ObjectCreateExpression _:
|
|
case AssignmentExpression _:
|
|
case ErrorExpression _:
|
|
return true;
|
|
case UnaryOperatorExpression uoe:
|
|
switch (uoe.Operator)
|
|
{
|
|
case UnaryOperatorType.PostIncrement:
|
|
case UnaryOperatorType.PostDecrement:
|
|
case UnaryOperatorType.Increment:
|
|
case UnaryOperatorType.Decrement:
|
|
case UnaryOperatorType.Await:
|
|
return true;
|
|
case UnaryOperatorType.NullConditionalRewrap:
|
|
return IsValidInStatementExpression(uoe.Expression);
|
|
default:
|
|
return false;
|
|
}
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region FindInsertionPoints
|
|
List<(InsertionPoint InsertionPoint, BlockContainer Scope)> scopeTracking = new List<(InsertionPoint, BlockContainer)>();
|
|
|
|
/// <summary>
|
|
/// Finds insertion points for all variables used within `node`
|
|
/// and adds them to the variableDict.
|
|
///
|
|
/// `level` == nesting depth of `node` within root node.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Insertion point for a variable = common parent of all uses of that variable
|
|
/// = smallest possible scope that contains all the uses of the variable
|
|
/// </remarks>
|
|
void FindInsertionPoints(AstNode node, int nodeLevel)
|
|
{
|
|
BlockContainer scope = node.Annotation<BlockContainer>();
|
|
if (scope != null && IsRelevantScope(scope))
|
|
{
|
|
// track loops and function bodies as scopes, for comparison with CaptureScope.
|
|
scopeTracking.Add((new InsertionPoint { level = nodeLevel, nextNode = node }, scope));
|
|
}
|
|
else
|
|
{
|
|
scope = null; // don't remove a scope if we didn't add one
|
|
}
|
|
try
|
|
{
|
|
for (AstNode child = node.FirstChild; child != null; child = child.NextSibling)
|
|
{
|
|
FindInsertionPoints(child, nodeLevel + 1);
|
|
}
|
|
if (node is IdentifierExpression identExpr)
|
|
{
|
|
var rr = identExpr.GetResolveResult() as ILVariableResolveResult;
|
|
if (rr != null && VariableNeedsDeclaration(rr.Variable.Kind))
|
|
{
|
|
FindInsertionPointForVariable(rr.Variable);
|
|
}
|
|
else if (identExpr.Annotation<ILFunction>() is ILFunction localFunction && localFunction.Kind == ILFunctionKind.LocalFunction)
|
|
{
|
|
foreach (var v in localFunction.CapturedVariables)
|
|
{
|
|
if (VariableNeedsDeclaration(v.Kind))
|
|
FindInsertionPointForVariable(v);
|
|
}
|
|
}
|
|
|
|
void FindInsertionPointForVariable(ILVariable variable)
|
|
{
|
|
InsertionPoint newPoint;
|
|
int startIndex = scopeTracking.Count - 1;
|
|
BlockContainer captureScope = variable.CaptureScope;
|
|
while (captureScope != null && !IsRelevantScope(captureScope))
|
|
{
|
|
captureScope = BlockContainer.FindClosestContainer(captureScope.Parent);
|
|
}
|
|
if (captureScope != null && startIndex > 0 && captureScope != scopeTracking[startIndex].Scope)
|
|
{
|
|
while (startIndex > 0 && scopeTracking[startIndex].Scope != captureScope)
|
|
startIndex--;
|
|
newPoint = scopeTracking[startIndex + 1].InsertionPoint;
|
|
}
|
|
else
|
|
{
|
|
newPoint = new InsertionPoint { level = nodeLevel, nextNode = identExpr };
|
|
if (variable.UsesInitialValue)
|
|
{
|
|
// Uninitialized variables are logically initialized at the beginning of the function
|
|
// Because it's possible that the variable has a loop-carried dependency,
|
|
// declare it outside of any loops.
|
|
while (startIndex >= 0)
|
|
{
|
|
if (scopeTracking[startIndex].Scope.EntryPoint.IncomingEdgeCount > 1)
|
|
{
|
|
// declare variable outside of loop
|
|
newPoint = scopeTracking[startIndex].InsertionPoint;
|
|
}
|
|
else if (scopeTracking[startIndex].Scope.Parent is ILFunction)
|
|
{
|
|
// stop at beginning of function
|
|
break;
|
|
}
|
|
startIndex--;
|
|
}
|
|
}
|
|
}
|
|
if (variableDict.TryGetValue(variable, out VariableToDeclare v))
|
|
{
|
|
v.InsertionPoint = FindCommonParent(v.InsertionPoint, newPoint);
|
|
}
|
|
else
|
|
{
|
|
v = new VariableToDeclare(variable, newPoint,
|
|
identExpr, sourceOrder: variableDict.Count);
|
|
variableDict.Add(variable, v);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (scope != null)
|
|
scopeTracking.RemoveAt(scopeTracking.Count - 1);
|
|
}
|
|
}
|
|
|
|
private static bool IsRelevantScope(BlockContainer scope)
|
|
{
|
|
return scope.EntryPoint.IncomingEdgeCount > 1 || scope.Parent is ILFunction;
|
|
}
|
|
|
|
internal static bool VariableNeedsDeclaration(VariableKind kind)
|
|
{
|
|
switch (kind)
|
|
{
|
|
case VariableKind.PinnedRegionLocal:
|
|
case VariableKind.Parameter:
|
|
case VariableKind.ExceptionLocal:
|
|
case VariableKind.ExceptionStackSlot:
|
|
case VariableKind.UsingLocal:
|
|
case VariableKind.ForeachLocal:
|
|
case VariableKind.PatternLocal:
|
|
return false;
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds an insertion point in a common parent instruction.
|
|
/// </summary>
|
|
InsertionPoint FindCommonParent(InsertionPoint oldPoint, InsertionPoint newPoint)
|
|
{
|
|
// First ensure we're looking at nodes on the same level:
|
|
oldPoint = oldPoint.UpTo(newPoint.level);
|
|
newPoint = newPoint.UpTo(oldPoint.level);
|
|
Debug.Assert(newPoint.level == oldPoint.level);
|
|
// Then go up the tree until both points share the same parent:
|
|
while (oldPoint.nextNode.Parent != newPoint.nextNode.Parent)
|
|
{
|
|
oldPoint = oldPoint.Up();
|
|
newPoint = newPoint.Up();
|
|
}
|
|
// return oldPoint as that one comes first in the source code
|
|
return oldPoint;
|
|
}
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Some variable declarations in C# are illegal (colliding),
|
|
/// even though the variable live ranges are not overlapping.
|
|
///
|
|
/// Multiple declarations in same block:
|
|
/// <code>
|
|
/// int i = 1; use(1);
|
|
/// int i = 2; use(2);
|
|
/// </code>
|
|
///
|
|
/// "Hiding" declaration in nested block:
|
|
/// <code>
|
|
/// int i = 1; use(1);
|
|
/// if (...) {
|
|
/// int i = 2; use(2);
|
|
/// }
|
|
/// </code>
|
|
///
|
|
/// Nested blocks are illegal even if the parent block
|
|
/// declares the variable later:
|
|
/// <code>
|
|
/// if (...) {
|
|
/// int i = 1; use(i);
|
|
/// }
|
|
/// int i = 2; use(i);
|
|
/// </code>
|
|
///
|
|
/// ResolveCollisions() detects all these cases, and combines the variable declarations
|
|
/// to a single declaration that is usable for the combined scopes.
|
|
/// </summary>
|
|
void ResolveCollisions()
|
|
{
|
|
var multiDict = new MultiDictionary<string, VariableToDeclare>();
|
|
foreach (var v in variableDict.Values)
|
|
{
|
|
// We can only insert variable declarations in blocks, but FindInsertionPoints() didn't
|
|
// guarantee that it finds only blocks.
|
|
// Fix that up now.
|
|
while (!(v.InsertionPoint.nextNode.Parent is BlockStatement))
|
|
{
|
|
if (v.InsertionPoint.nextNode.Parent is ForStatement f && v.InsertionPoint.nextNode == f.Initializers.FirstOrDefault() && IsMatchingAssignment(v, out _))
|
|
{
|
|
// Special case: the initializer of a ForStatement can also declare a variable (with scope local to the for loop).
|
|
break;
|
|
}
|
|
v.InsertionPoint = v.InsertionPoint.Up();
|
|
}
|
|
// Note: 'out var', pattern matching etc. is not considered a valid insertion point here, because the scope of the
|
|
// resulting variable is not restricted to the parent node of the insertion point, but extends to the whole BlockStatement.
|
|
// We moved up the insertion point to the whole BlockStatement so that we can resolve collisions,
|
|
// later we might decide to declare the variable more locally (as 'out var') instead if still possible.
|
|
|
|
// Go through all potentially colliding variables:
|
|
foreach (var prev in multiDict[v.Name])
|
|
{
|
|
if (prev.RemovedDueToCollision)
|
|
continue;
|
|
// Go up until both nodes are on the same level:
|
|
InsertionPoint point1 = prev.InsertionPoint.UpTo(v.InsertionPoint.level);
|
|
InsertionPoint point2 = v.InsertionPoint.UpTo(prev.InsertionPoint.level);
|
|
Debug.Assert(point1.level == point2.level);
|
|
if (point1.nextNode.Parent == point2.nextNode.Parent)
|
|
{
|
|
Debug.Assert(prev.Type.Equals(v.Type));
|
|
// We found a collision!
|
|
v.InvolvedInCollision = true;
|
|
prev.ReplacementDueToCollision = v;
|
|
// Continue checking other entries in multiDict against the new position of `v`.
|
|
if (prev.SourceOrder < v.SourceOrder)
|
|
{
|
|
// Switch v's insertion point to prev's insertion point:
|
|
v.InsertionPoint = point1;
|
|
// Since prev was first, it has the correct SourceOrder/FirstUse values
|
|
// for the new combined variable:
|
|
v.SourceOrder = prev.SourceOrder;
|
|
v.FirstUse = prev.FirstUse;
|
|
}
|
|
else
|
|
{
|
|
// v is first in source order, so it keeps its old insertion point
|
|
// (and other properties), except that the insertion point is
|
|
// moved up to prev's level.
|
|
v.InsertionPoint = point2;
|
|
}
|
|
v.DefaultInitialization |= prev.DefaultInitialization;
|
|
// I think we don't need to re-check the dict entries that we already checked earlier,
|
|
// because the new v.InsertionPoint only collides with another point x if either
|
|
// the old v.InsertionPoint or the old prev.InsertionPoint already collided with x.
|
|
}
|
|
}
|
|
|
|
multiDict.Add(v.Name, v);
|
|
}
|
|
}
|
|
|
|
private void InsertDeconstructionVariableDeclarations()
|
|
{
|
|
var usedVariables = new HashSet<ILVariable>();
|
|
foreach (var g in variableDict.Values.GroupBy(v => v.InsertionPoint.nextNode))
|
|
{
|
|
if (!(g.Key is ExpressionStatement { Expression: AssignmentExpression { Left: TupleExpression left, Operator: AssignmentOperatorType.Assign } assignment }))
|
|
continue;
|
|
usedVariables.Clear();
|
|
var deconstruct = assignment.Annotation<DeconstructInstruction>();
|
|
if (deconstruct == null || deconstruct.Init.Count > 0 || deconstruct.Conversions.Instructions.Count > 0)
|
|
continue;
|
|
if (!deconstruct.Assignments.Instructions.All(IsDeclarableVariable))
|
|
continue;
|
|
|
|
var designation = StatementBuilder.TranslateDeconstructionDesignation(deconstruct, isForeach: false);
|
|
left.ReplaceWith(new DeclarationExpression { Type = new SimpleType("var"), Designation = designation });
|
|
|
|
foreach (var v in usedVariables)
|
|
{
|
|
variableDict[v].DeclaredInDeconstruction = true;
|
|
}
|
|
|
|
bool IsDeclarableVariable(ILInstruction inst)
|
|
{
|
|
if (!inst.MatchStLoc(out var v, out var value))
|
|
return false;
|
|
if (!g.Any(vd => vd.ILVariable == v && !vd.RemovedDueToCollision))
|
|
return false;
|
|
if (!usedVariables.Add(v))
|
|
return false;
|
|
var expectedType = ((LdLoc)value).Variable.Type;
|
|
if (!v.Type.Equals(expectedType))
|
|
return false;
|
|
if (!(v.Kind == VariableKind.StackSlot || v.Kind == VariableKind.Local))
|
|
return false;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool IsMatchingAssignment(VariableToDeclare v, out AssignmentExpression assignment)
|
|
{
|
|
assignment = v.InsertionPoint.nextNode as AssignmentExpression;
|
|
if (assignment == null)
|
|
{
|
|
assignment = (v.InsertionPoint.nextNode as ExpressionStatement)?.Expression as AssignmentExpression;
|
|
if (assignment == null)
|
|
return false;
|
|
}
|
|
return assignment.Operator == AssignmentOperatorType.Assign
|
|
&& assignment.Left is IdentifierExpression identExpr
|
|
&& identExpr.Identifier == v.Name
|
|
&& identExpr.TypeArguments.Count == 0;
|
|
}
|
|
|
|
bool CombineDeclarationAndInitializer(VariableToDeclare v, TransformContext context)
|
|
{
|
|
if (v.Type.IsByRefLike)
|
|
return true; // by-ref-like variables always must be initialized at their declaration.
|
|
|
|
if (v.InsertionPoint.nextNode.Role == ForStatement.InitializerRole)
|
|
return true; // for-statement initializers always should combine declaration and initialization.
|
|
|
|
return !context.Settings.SeparateLocalVariableDeclarations;
|
|
}
|
|
|
|
void InsertVariableDeclarations(TransformContext context)
|
|
{
|
|
var replacements = new List<(AstNode, AstNode)>();
|
|
foreach (var (ilVariable, v) in variableDict)
|
|
{
|
|
if (v.RemovedDueToCollision || v.DeclaredInDeconstruction)
|
|
continue;
|
|
|
|
if (CombineDeclarationAndInitializer(v, context) && IsMatchingAssignment(v, out AssignmentExpression assignment))
|
|
{
|
|
// 'int v; v = expr;' can be combined to 'int v = expr;'
|
|
AstType type;
|
|
if (context.Settings.AnonymousTypes && v.Type.ContainsAnonymousType())
|
|
{
|
|
type = new SimpleType("var");
|
|
}
|
|
else
|
|
{
|
|
type = context.TypeSystemAstBuilder.ConvertType(v.Type);
|
|
}
|
|
if (v.ILVariable.IsRefReadOnly && type is ComposedType composedType && composedType.HasRefSpecifier)
|
|
{
|
|
composedType.HasReadOnlySpecifier = true;
|
|
}
|
|
if (v.ILVariable.Kind == VariableKind.PinnedLocal)
|
|
{
|
|
type.InsertChildAfter(null, new Comment("pinned", CommentType.MultiLine), Roles.Comment);
|
|
}
|
|
var vds = new VariableDeclarationStatement(type, v.Name, assignment.Right.Detach());
|
|
var init = vds.Variables.Single();
|
|
init.AddAnnotation(assignment.Left.GetResolveResult());
|
|
foreach (object annotation in assignment.Left.Annotations.Concat(assignment.Annotations))
|
|
{
|
|
if (!(annotation is ResolveResult))
|
|
{
|
|
init.AddAnnotation(annotation);
|
|
}
|
|
}
|
|
replacements.Add((v.InsertionPoint.nextNode, vds));
|
|
}
|
|
else if (CanBeDeclaredAsOutVariable(v, out var dirExpr))
|
|
{
|
|
// '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<UseImplicitlyTypedOutAnnotation>() != null)
|
|
{
|
|
type = new SimpleType("var");
|
|
}
|
|
else
|
|
{
|
|
type = context.TypeSystemAstBuilder.ConvertType(v.Type);
|
|
}
|
|
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));
|
|
}
|
|
else
|
|
{
|
|
// Insert a separate declaration statement.
|
|
Expression initializer = null;
|
|
AstType type = context.TypeSystemAstBuilder.ConvertType(v.Type);
|
|
if (v.DefaultInitialization == VariableInitKind.NeedsDefaultValue)
|
|
{
|
|
initializer = new DefaultValueExpression(type.Clone());
|
|
}
|
|
var vds = new VariableDeclarationStatement(type, v.Name, initializer);
|
|
vds.Variables.Single().AddAnnotation(new ILVariableResolveResult(ilVariable));
|
|
Debug.Assert(v.InsertionPoint.nextNode.Role == BlockStatement.StatementRole);
|
|
if (v.DefaultInitialization == VariableInitKind.NeedsSkipInit)
|
|
{
|
|
AstType unsafeType = context.TypeSystemAstBuilder.ConvertType(
|
|
context.TypeSystem.FindType(KnownTypeCode.Unsafe));
|
|
if (context.Settings.OutVariables)
|
|
{
|
|
var outVarDecl = new OutVarDeclarationExpression(type.Clone(), v.Name);
|
|
outVarDecl.Variable.AddAnnotation(new ILVariableResolveResult(ilVariable));
|
|
v.InsertionPoint.nextNode.Parent.InsertChildBefore(
|
|
v.InsertionPoint.nextNode,
|
|
new ExpressionStatement {
|
|
Expression = new InvocationExpression {
|
|
Target = new MemberReferenceExpression {
|
|
Target = new TypeReferenceExpression(unsafeType),
|
|
MemberName = "SkipInit"
|
|
},
|
|
Arguments = {
|
|
outVarDecl
|
|
}
|
|
}
|
|
},
|
|
BlockStatement.StatementRole);
|
|
}
|
|
else
|
|
{
|
|
v.InsertionPoint.nextNode.Parent.InsertChildBefore(
|
|
v.InsertionPoint.nextNode,
|
|
vds,
|
|
BlockStatement.StatementRole);
|
|
v.InsertionPoint.nextNode.Parent.InsertChildBefore(
|
|
v.InsertionPoint.nextNode,
|
|
new ExpressionStatement {
|
|
Expression = new InvocationExpression {
|
|
Target = new MemberReferenceExpression {
|
|
Target = new TypeReferenceExpression(unsafeType),
|
|
MemberName = "SkipInit"
|
|
},
|
|
Arguments = {
|
|
new DirectionExpression(
|
|
FieldDirection.Out,
|
|
new IdentifierExpression(v.Name)
|
|
.WithRR(new ILVariableResolveResult(ilVariable))
|
|
)
|
|
}
|
|
}
|
|
},
|
|
BlockStatement.StatementRole);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
v.InsertionPoint.nextNode.Parent.InsertChildBefore(
|
|
v.InsertionPoint.nextNode,
|
|
vds,
|
|
BlockStatement.StatementRole);
|
|
}
|
|
}
|
|
}
|
|
// perform replacements at end, so that we don't replace a node while it is still referenced by a VariableToDeclare
|
|
foreach (var (oldNode, newNode) in replacements)
|
|
{
|
|
oldNode.ReplaceWith(newNode);
|
|
}
|
|
}
|
|
|
|
private bool CanBeDeclaredAsOutVariable(VariableToDeclare v, out DirectionExpression dirExpr)
|
|
{
|
|
dirExpr = v.FirstUse.Parent as DirectionExpression;
|
|
if (dirExpr == null || dirExpr.FieldDirection != FieldDirection.Out)
|
|
return false;
|
|
if (!context.Settings.OutVariables)
|
|
return false;
|
|
if (v.DefaultInitialization != VariableInitKind.None)
|
|
return false;
|
|
for (AstNode node = v.FirstUse; node != null; node = node.Parent)
|
|
{
|
|
if (node.Role == Roles.EmbeddedStatement)
|
|
{
|
|
return false;
|
|
}
|
|
switch (node)
|
|
{
|
|
case IfElseStatement _: // variable declared in if condition appears in parent scope
|
|
case ExpressionStatement _:
|
|
return node == v.InsertionPoint.nextNode;
|
|
case Statement _:
|
|
return false; // other statements (e.g. while) don't allow variables to be promoted to parent scope
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update ILVariableResolveResult annotations of all ILVariables that have been replaced by ResolveCollisions.
|
|
/// </summary>
|
|
void UpdateAnnotations(AstNode rootNode)
|
|
{
|
|
foreach (var node in rootNode.Descendants)
|
|
{
|
|
ILVariable ilVar;
|
|
switch (node)
|
|
{
|
|
case IdentifierExpression id:
|
|
ilVar = id.GetILVariable();
|
|
break;
|
|
case VariableInitializer vi:
|
|
ilVar = vi.GetILVariable();
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
if (ilVar == null || !VariableNeedsDeclaration(ilVar.Kind))
|
|
continue;
|
|
var v = variableDict[ilVar];
|
|
if (!v.RemovedDueToCollision)
|
|
continue;
|
|
while (v.RemovedDueToCollision)
|
|
{
|
|
v = v.ReplacementDueToCollision;
|
|
}
|
|
node.RemoveAnnotations<ILVariableResolveResult>();
|
|
node.AddAnnotation(new ILVariableResolveResult(v.ILVariable, v.Type));
|
|
}
|
|
}
|
|
}
|
|
}
|