Browse Source

Add support for 'with' expressions

pull/2276/head
Siegfried Pammer 4 years ago
parent
commit
3df82cf33b
  1. 32
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs
  2. 22
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs
  3. 32
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  4. 9
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
  5. 15
      ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs
  6. 67
      ICSharpCode.Decompiler/CSharp/Syntax/Expressions/WithInitializerExpression.cs
  7. 3
      ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs
  8. 19
      ICSharpCode.Decompiler/DecompilerSettings.cs
  9. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  10. 6
      ICSharpCode.Decompiler/IL/Instructions/Block.cs
  11. 228
      ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs
  12. 4
      ILSpy/Languages/CSharpHighlightingTokenWriter.cs
  13. 9
      ILSpy/Properties/Resources.Designer.cs
  14. 3
      ILSpy/Properties/Resources.resx

32
ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs

@ -214,6 +214,13 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests @@ -214,6 +214,13 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests
get;
set;
}
#if CS90
public Fields Value7 {
get;
set;
}
#endif
}
public class OtherItem
@ -277,6 +284,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests @@ -277,6 +284,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests
z = _z;
}
}
#if CS90
public record Fields
{
public int A;
public double B = 1.0;
public object C;
public dynamic D;
public string S = "abc";
public Item I;
}
#endif
#endregion
#region Field initializer tests
@ -1547,6 +1566,19 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests @@ -1547,6 +1566,19 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests
return null;
}
#if CS90
private Fields RecordWithNestedClass(Fields input)
{
return input with {
A = 42,
I = new Item {
Value7 = input with {
A = 43
}
}
}
}
#endif
#endregion
#region Collection initializer

22
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs

@ -54,6 +54,28 @@ @@ -54,6 +54,28 @@
}
}
public class WithExpressionTests
{
public Fields Test(Fields input)
{
return input with {
A = 42,
B = 3.141,
C = input
};
}
public Fields Test2(Fields input)
{
return input with {
A = 42,
B = 3.141,
C = input with {
A = 43
}
};
}
}
public abstract record WithNestedRecords
{
public record A : WithNestedRecords

32
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -790,8 +790,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -790,8 +790,7 @@ namespace ICSharpCode.Decompiler.CSharp
{
// Reference comparison using Unsafe intrinsics
Debug.Assert(!inst.IsLifted);
(string methodName, bool negate) = inst.Kind switch
{
(string methodName, bool negate) = inst.Kind switch {
ComparisonKind.Equality => ("AreSame", false),
ComparisonKind.Inequality => ("AreSame", true),
ComparisonKind.LessThan => ("IsAddressLessThan", false),
@ -3052,6 +3051,8 @@ namespace ICSharpCode.Decompiler.CSharp @@ -3052,6 +3051,8 @@ namespace ICSharpCode.Decompiler.CSharp
case BlockKind.CollectionInitializer:
case BlockKind.ObjectInitializer:
return TranslateObjectAndCollectionInitializer(block);
case BlockKind.WithInitializer:
return TranslateWithInitializer(block);
case BlockKind.CallInlineAssign:
return TranslateSetterCallAssignment(block);
case BlockKind.CallWithNamedArgs:
@ -3112,7 +3113,13 @@ namespace ICSharpCode.Decompiler.CSharp @@ -3112,7 +3113,13 @@ namespace ICSharpCode.Decompiler.CSharp
default:
throw new ArgumentException("given Block is invalid!");
}
// Build initializer expression
var oce = (ObjectCreateExpression)expr.Expression;
oce.Initializer = BuildArrayInitializerExpression(block, initObjRR);
return expr.WithILInstruction(block);
}
private ArrayInitializerExpression BuildArrayInitializerExpression(Block block, InitializedObjectResolveResult initObjRR)
{
var elementsStack = new Stack<List<TranslatedExpression>>();
var elements = new List<TranslatedExpression>(block.Instructions.Count);
elementsStack.Push(elements);
@ -3199,9 +3206,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -3199,9 +3206,7 @@ namespace ICSharpCode.Decompiler.CSharp
MakeInitializerAssignment(initObjRR, methodElement, pathElement, values, indexVariables)
);
}
var oce = (ObjectCreateExpression)expr.Expression;
oce.Initializer = new ArrayInitializerExpression(elements.SelectArray(e => e.Expression));
return expr.WithILInstruction(block);
return new ArrayInitializerExpression(elements.SelectArray(e => e.Expression));
}
IEnumerable<ILInstruction> GetIndices(IEnumerable<ILInstruction> indices, Dictionary<ILVariable, ILInstruction> indexVariables)
@ -3427,6 +3432,21 @@ namespace ICSharpCode.Decompiler.CSharp @@ -3427,6 +3432,21 @@ namespace ICSharpCode.Decompiler.CSharp
.WithRR(new ResolveResult(stloc.Variable.Type));
}
TranslatedExpression TranslateWithInitializer(Block block)
{
var stloc = block.Instructions.FirstOrDefault() as StLoc;
var final = block.FinalInstruction as LdLoc;
if (stloc == null || final == null || stloc.Variable != final.Variable || stloc.Variable.Kind != VariableKind.InitializerTarget)
throw new ArgumentException("given Block is invalid!");
WithInitializerExpression withInitializerExpression = new WithInitializerExpression();
withInitializerExpression.Expression = Translate(stloc.Value, stloc.Variable.Type);
withInitializerExpression.Initializer = BuildArrayInitializerExpression(block, new InitializedObjectResolveResult(stloc.Variable.Type));
return withInitializerExpression.WithILInstruction(block)
.WithRR(new ResolveResult(stloc.Variable.Type));
}
/// <summary>
/// If expr is a constant integer expression, and its value fits into type,
/// convert the expression into the target type.

9
ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs

@ -1224,6 +1224,15 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -1224,6 +1224,15 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
EndNode(uncheckedExpression);
}
public virtual void VisitWithInitializerExpression(WithInitializerExpression withInitializerExpression)
{
StartNode(withInitializerExpression);
withInitializerExpression.Expression.AcceptVisitor(this);
WriteKeyword("with", WithInitializerExpression.WithKeywordRole);
withInitializerExpression.Initializer.AcceptVisitor(this);
EndNode(withInitializerExpression);
}
#endregion
#region Query Expressions

15
ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs

@ -691,6 +691,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -691,6 +691,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
{
VisitChildren(placeholder);
}
public virtual void VisitWithInitializerExpression(WithInitializerExpression withInitializerExpression)
{
VisitChildren(withInitializerExpression);
}
}
/// <summary>
@ -1359,6 +1364,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -1359,6 +1364,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
{
return VisitChildren(placeholder);
}
public virtual T VisitWithInitializerExpression(WithInitializerExpression withInitializerExpression)
{
return VisitChildren(withInitializerExpression);
}
}
/// <summary>
@ -2027,5 +2037,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -2027,5 +2037,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
{
return VisitChildren(placeholder, data);
}
public virtual S VisitWithInitializerExpression(WithInitializerExpression withInitializerExpression, T data)
{
return VisitChildren(withInitializerExpression, data);
}
}
}

67
ICSharpCode.Decompiler/CSharp/Syntax/Expressions/WithInitializerExpression.cs

@ -0,0 +1,67 @@ @@ -0,0 +1,67 @@
// Copyright (c) 2010-2021 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 ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching;
namespace ICSharpCode.Decompiler.CSharp.Syntax
{
/// <summary>
/// Expression with Initializer
/// </summary>
public class WithInitializerExpression : Expression
{
public readonly static TokenRole WithKeywordRole = new TokenRole("with");
public readonly static Role<ArrayInitializerExpression> InitializerRole = ArrayCreateExpression.InitializerRole;
public Expression Expression {
get { return GetChildByRole(Roles.Expression); }
set { SetChildByRole(Roles.Expression, value); }
}
public CSharpTokenNode WithToken {
get { return GetChildByRole(WithKeywordRole); }
}
public ArrayInitializerExpression Initializer {
get { return GetChildByRole(InitializerRole); }
set { SetChildByRole(InitializerRole, value); }
}
public override void AcceptVisitor(IAstVisitor visitor)
{
visitor.VisitWithInitializerExpression(this);
}
public override T AcceptVisitor<T>(IAstVisitor<T> visitor)
{
return visitor.VisitWithInitializerExpression(this);
}
public override S AcceptVisitor<T, S>(IAstVisitor<T, S> visitor, T data)
{
return visitor.VisitWithInitializerExpression(this, data);
}
protected internal override bool DoMatch(AstNode other, Match match)
{
return other is WithInitializerExpression o
&& this.Expression.DoMatch(o.Expression, match)
&& this.Initializer.DoMatch(o.Initializer, match);
}
}
}

3
ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs

@ -63,6 +63,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -63,6 +63,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
void VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression);
void VisitUncheckedExpression(UncheckedExpression uncheckedExpression);
void VisitUndocumentedExpression(UndocumentedExpression undocumentedExpression);
void VisitWithInitializerExpression(WithInitializerExpression withInitializerExpression);
void VisitQueryExpression(QueryExpression queryExpression);
void VisitQueryContinuationClause(QueryContinuationClause queryContinuationClause);
@ -209,6 +210,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -209,6 +210,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
S VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression);
S VisitUncheckedExpression(UncheckedExpression uncheckedExpression);
S VisitUndocumentedExpression(UndocumentedExpression undocumentedExpression);
S VisitWithInitializerExpression(WithInitializerExpression withInitializerExpression);
S VisitQueryExpression(QueryExpression queryExpression);
S VisitQueryContinuationClause(QueryContinuationClause queryContinuationClause);
@ -355,6 +357,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -355,6 +357,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
S VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression, T data);
S VisitUncheckedExpression(UncheckedExpression uncheckedExpression, T data);
S VisitUndocumentedExpression(UndocumentedExpression undocumentedExpression, T data);
S VisitWithInitializerExpression(WithInitializerExpression withInitializerExpression, T data);
S VisitQueryExpression(QueryExpression queryExpression, T data);
S VisitQueryContinuationClause(QueryContinuationClause queryContinuationClause, T data);

19
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -136,6 +136,7 @@ namespace ICSharpCode.Decompiler @@ -136,6 +136,7 @@ namespace ICSharpCode.Decompiler
functionPointers = false;
forEachWithGetEnumeratorExtension = false;
recordClasses = false;
withExpressions = false;
}
}
@ -225,6 +226,24 @@ namespace ICSharpCode.Decompiler @@ -225,6 +226,24 @@ namespace ICSharpCode.Decompiler
}
}
bool withExpressions = true;
/// <summary>
/// Use C# 9 <c>with</c> initializer expressions.
/// </summary>
[Category("C# 9.0 / VS 2019.8")]
[Description("DecompilerSettings.WithExpressions")]
public bool WithExpressions {
get { return withExpressions; }
set {
if (withExpressions != value)
{
withExpressions = value;
OnPropertyChanged();
}
}
}
bool functionPointers = true;
/// <summary>

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -73,6 +73,7 @@ @@ -73,6 +73,7 @@
<Compile Include="CSharp\ProjectDecompiler\TargetServices.cs" />
<Compile Include="CSharp\Syntax\Expressions\DeclarationExpression.cs" />
<Compile Include="CSharp\Syntax\Expressions\SwitchExpression.cs" />
<Compile Include="CSharp\Syntax\Expressions\WithInitializerExpression.cs" />
<Compile Include="CSharp\Syntax\FunctionPointerAstType.cs" />
<Compile Include="CSharp\Syntax\VariableDesignation.cs" />
<Compile Include="Humanizer\Vocabularies.cs" />

6
ICSharpCode.Decompiler/IL/Instructions/Block.cs

@ -158,7 +158,10 @@ namespace ICSharpCode.Decompiler.IL @@ -158,7 +158,10 @@ namespace ICSharpCode.Decompiler.IL
IType type2 = null;
bool condition = Instructions[0].MatchStLoc(final2.Variable, out var init2);
Debug.Assert(condition);
Debug.Assert(init2 is NewObj || init2 is DefaultValue || (init2 is Block named && named.Kind == BlockKind.CallWithNamedArgs));
Debug.Assert(init2 is NewObj
|| init2 is DefaultValue
|| (init2 is CallInstruction ci && TransformCollectionAndObjectInitializers.IsRecordCloneMethodCall(ci))
|| (init2 is Block named && named.Kind == BlockKind.CallWithNamedArgs));
switch (init2)
{
case NewObj newObj:
@ -454,5 +457,6 @@ namespace ICSharpCode.Decompiler.IL @@ -454,5 +457,6 @@ namespace ICSharpCode.Decompiler.IL
/// <see cref="DeconstructInstruction"/>
/// </summary>
DeconstructionAssignments,
WithInitializer,
}
}

228
ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs

@ -54,114 +54,135 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -54,114 +54,135 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{
ILInstruction inst = body.Instructions[pos];
// Match stloc(v, newobj)
if (inst.MatchStLoc(out var v, out var initInst) && (v.Kind == VariableKind.Local || v.Kind == VariableKind.StackSlot))
if (!inst.MatchStLoc(out var v, out var initInst) || v.Kind != VariableKind.Local && v.Kind != VariableKind.StackSlot)
return false;
IType instType;
var blockKind = BlockKind.CollectionInitializer;
var insertionPos = initInst.ChildIndex;
var siblings = initInst.Parent.Children;
switch (initInst)
{
IType instType;
switch (initInst)
{
case NewObj newObjInst:
if (newObjInst.ILStackWasEmpty && v.Kind == VariableKind.Local && !context.Function.Method.IsConstructor && !context.Function.Method.IsCompilerGeneratedOrIsInCompilerGeneratedClass())
{
// on statement level (no other expressions on IL stack),
// prefer to keep local variables (but not stack slots),
// unless we are in a constructor (where inlining object initializers might be critical
// for the base ctor call) or a compiler-generated delegate method, which might be used in a query expression.
return false;
}
// Do not try to transform delegate construction.
// DelegateConstruction transform cannot deal with this.
if (DelegateConstruction.MatchDelegateConstruction(newObjInst, out _, out _, out _) || TransformDisplayClassUsage.IsPotentialClosure(context, newObjInst))
return false;
// Cannot build a collection/object initializer attached to an AnonymousTypeCreateExpression:s
// anon = new { A = 5 } { 3,4,5 } is invalid syntax.
if (newObjInst.Method.DeclaringType.ContainsAnonymousType())
return false;
instType = newObjInst.Method.DeclaringType;
break;
case DefaultValue defaultVal:
if (defaultVal.ILStackWasEmpty && v.Kind == VariableKind.Local && !context.Function.Method.IsConstructor)
{
// on statement level (no other expressions on IL stack),
// prefer to keep local variables (but not stack slots),
// unless we are in a constructor (where inlining object initializers might be critical
// for the base ctor call)
return false;
}
instType = defaultVal.Type;
break;
default:
case NewObj newObjInst:
if (newObjInst.ILStackWasEmpty && v.Kind == VariableKind.Local
&& !context.Function.Method.IsConstructor
&& !context.Function.Method.IsCompilerGeneratedOrIsInCompilerGeneratedClass())
{
// on statement level (no other expressions on IL stack),
// prefer to keep local variables (but not stack slots),
// unless we are in a constructor (where inlining object initializers might be critical
// for the base ctor call) or a compiler-generated delegate method, which might be used in a query expression.
return false;
}
int initializerItemsCount = 0;
var blockKind = BlockKind.CollectionInitializer;
possibleIndexVariables = new Dictionary<ILVariable, (int Index, ILInstruction Value)>();
currentPath = new List<AccessPathElement>();
isCollection = false;
pathStack = new Stack<HashSet<AccessPathElement>>();
pathStack.Push(new HashSet<AccessPathElement>());
// Detect initializer type by scanning the following statements
// each must be a callvirt with ldloc v as first argument
// if the method is a setter we're dealing with an object initializer
// if the method is named Add and has at least 2 arguments we're dealing with a collection/dictionary initializer
while (pos + initializerItemsCount + 1 < body.Instructions.Count
&& IsPartOfInitializer(body.Instructions, pos + initializerItemsCount + 1, v, instType, ref blockKind))
{
initializerItemsCount++;
}
// Do not convert the statements into an initializer if there's an incompatible usage of the initializer variable
// directly after the possible initializer.
if (IsMethodCallOnVariable(body.Instructions[pos + initializerItemsCount + 1], v))
return false;
// Calculate the correct number of statements inside the initializer:
// All index variables that were used in the initializer have Index set to -1.
// We fetch the first unused variable from the list and remove all instructions after its
// first usage (i.e. the init store) from the initializer.
var index = possibleIndexVariables.Where(info => info.Value.Index > -1).Min(info => (int?)info.Value.Index);
if (index != null)
{
initializerItemsCount = index.Value - pos - 1;
}
// The initializer would be empty, there's nothing to do here.
if (initializerItemsCount <= 0)
return false;
context.Step("CollectionOrObjectInitializer", inst);
// Create a new block and final slot (initializer target variable)
var initializerBlock = new Block(blockKind);
ILVariable finalSlot = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type);
initializerBlock.FinalInstruction = new LdLoc(finalSlot);
initializerBlock.Instructions.Add(new StLoc(finalSlot, initInst.Clone()));
// Move all instructions to the initializer block.
for (int i = 1; i <= initializerItemsCount; i++)
{
switch (body.Instructions[i + pos])
}
// Do not try to transform delegate construction.
// DelegateConstruction transform cannot deal with this.
if (DelegateConstruction.MatchDelegateConstruction(newObjInst, out _, out _, out _)
|| TransformDisplayClassUsage.IsPotentialClosure(context, newObjInst))
return false;
// Cannot build a collection/object initializer attached to an AnonymousTypeCreateExpression
// anon = new { A = 5 } { 3,4,5 } is invalid syntax.
if (newObjInst.Method.DeclaringType.ContainsAnonymousType())
return false;
instType = newObjInst.Method.DeclaringType;
break;
case DefaultValue defaultVal:
if (defaultVal.ILStackWasEmpty && v.Kind == VariableKind.Local && !context.Function.Method.IsConstructor)
{
case CallInstruction call:
if (!(call is CallVirt || call is Call))
continue;
var newCall = call;
var newTarget = newCall.Arguments[0];
foreach (var load in newTarget.Descendants.OfType<IInstructionWithVariableOperand>())
if ((load is LdLoc || load is LdLoca) && load.Variable == v)
load.Variable = finalSlot;
initializerBlock.Instructions.Add(newCall);
break;
case StObj stObj:
var newStObj = stObj;
foreach (var load in newStObj.Target.Descendants.OfType<IInstructionWithVariableOperand>())
if ((load is LdLoc || load is LdLoca) && load.Variable == v)
load.Variable = finalSlot;
initializerBlock.Instructions.Add(newStObj);
break;
case StLoc stLoc:
var newStLoc = stLoc;
initializerBlock.Instructions.Add(newStLoc);
break;
// on statement level (no other expressions on IL stack),
// prefer to keep local variables (but not stack slots),
// unless we are in a constructor (where inlining object initializers might be
// critical for the base ctor call)
return false;
}
instType = defaultVal.Type;
break;
case CallInstruction ci when context.Settings.WithExpressions && IsRecordCloneMethodCall(ci):
instType = ci.Method.DeclaringType;
blockKind = BlockKind.WithInitializer;
initInst = ci.Arguments.Single();
break;
default:
return false;
}
int initializerItemsCount = 0;
possibleIndexVariables = new Dictionary<ILVariable, (int Index, ILInstruction Value)>();
currentPath = new List<AccessPathElement>();
isCollection = false;
pathStack = new Stack<HashSet<AccessPathElement>>();
pathStack.Push(new HashSet<AccessPathElement>());
// Detect initializer type by scanning the following statements
// each must be a callvirt with ldloc v as first argument
// if the method is a setter we're dealing with an object initializer
// if the method is named Add and has at least 2 arguments we're dealing with a collection/dictionary initializer
while (pos + initializerItemsCount + 1 < body.Instructions.Count
&& IsPartOfInitializer(body.Instructions, pos + initializerItemsCount + 1, v, instType, ref blockKind))
{
initializerItemsCount++;
}
// Do not convert the statements into an initializer if there's an incompatible usage of the initializer variable
// directly after the possible initializer.
if (IsMethodCallOnVariable(body.Instructions[pos + initializerItemsCount + 1], v))
return false;
// Calculate the correct number of statements inside the initializer:
// All index variables that were used in the initializer have Index set to -1.
// We fetch the first unused variable from the list and remove all instructions after its
// first usage (i.e. the init store) from the initializer.
var index = possibleIndexVariables.Where(info => info.Value.Index > -1).Min(info => (int?)info.Value.Index);
if (index != null)
{
initializerItemsCount = index.Value - pos - 1;
}
// The initializer would be empty, there's nothing to do here.
if (initializerItemsCount <= 0)
return false;
context.Step("CollectionOrObjectInitializer", inst);
// Create a new block and final slot (initializer target variable)
var initializerBlock = new Block(blockKind);
ILVariable finalSlot = context.Function.RegisterVariable(VariableKind.InitializerTarget, instType);
initializerBlock.FinalInstruction = new LdLoc(finalSlot);
initializerBlock.Instructions.Add(new StLoc(finalSlot, initInst));
// Move all instructions to the initializer block.
for (int i = 1; i <= initializerItemsCount; i++)
{
switch (body.Instructions[i + pos])
{
case CallInstruction call:
if (!(call is CallVirt || call is Call))
continue;
var newCall = call;
var newTarget = newCall.Arguments[0];
foreach (var load in newTarget.Descendants.OfType<IInstructionWithVariableOperand>())
if ((load is LdLoc || load is LdLoca) && load.Variable == v)
load.Variable = finalSlot;
initializerBlock.Instructions.Add(newCall);
break;
case StObj stObj:
var newStObj = stObj;
foreach (var load in newStObj.Target.Descendants.OfType<IInstructionWithVariableOperand>())
if ((load is LdLoc || load is LdLoca) && load.Variable == v)
load.Variable = finalSlot;
initializerBlock.Instructions.Add(newStObj);
break;
case StLoc stLoc:
var newStLoc = stLoc;
initializerBlock.Instructions.Add(newStLoc);
break;
}
initInst.ReplaceWith(initializerBlock);
body.Instructions.RemoveRange(pos + 1, initializerItemsCount);
ILInlining.InlineIfPossible(body, pos, context);
}
body.Instructions.RemoveRange(pos + 1, initializerItemsCount);
siblings[insertionPos] = initializerBlock;
ILInlining.InlineIfPossible(body, pos, context);
return true;
}
internal static bool IsRecordCloneMethodCall(CallInstruction ci)
{
if (ci.Method.DeclaringTypeDefinition?.IsRecord != true)
return false;
if (ci.Method.Name != "<Clone>$")
return false;
if (ci.Arguments.Count != 1)
return false;
return true;
}
@ -234,7 +255,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -234,7 +255,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
if (values.Count != 1 || !IsValidObjectInitializerTarget(currentPath))
return false;
blockKind = BlockKind.ObjectInitializer;
if (blockKind != BlockKind.ObjectInitializer && blockKind != BlockKind.WithInitializer)
blockKind = BlockKind.ObjectInitializer;
return true;
default:
return false;

4
ILSpy/Languages/CSharpHighlightingTokenWriter.cs

@ -171,6 +171,10 @@ namespace ICSharpCode.ILSpy @@ -171,6 +171,10 @@ namespace ICSharpCode.ILSpy
case "stackalloc":
color = typeKeywordsColor;
break;
case "with":
if (role == WithInitializerExpression.WithKeywordRole)
color = typeKeywordsColor;
break;
case "try":
case "throw":
case "catch":

9
ILSpy/Properties/Resources.Designer.cs generated

@ -1334,6 +1334,15 @@ namespace ICSharpCode.ILSpy.Properties { @@ -1334,6 +1334,15 @@ namespace ICSharpCode.ILSpy.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to &apos;with&apos; initializer expressions.
/// </summary>
public static string DecompilerSettings_WithExpressions {
get {
return ResourceManager.GetString("DecompilerSettings.WithExpressions", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The settings selected below are applied to the decompiler output in combination with the selection in the language drop-down. Selecting a lower language version in the drop-down will deactivate all selected options of the higher versions. Note that some settings implicitly depend on each other, e.g.: LINQ expressions cannot be introduced without first transforming static calls to extension method calls..
/// </summary>

3
ILSpy/Properties/Resources.resx

@ -474,6 +474,9 @@ Are you sure you want to continue?</value> @@ -474,6 +474,9 @@ Are you sure you want to continue?</value>
<data name="DecompilerSettings.VBSpecificOptions" xml:space="preserve">
<value>VB-specific options</value>
</data>
<data name="DecompilerSettings.WithExpressions" xml:space="preserve">
<value>'with' initializer expressions</value>
</data>
<data name="DecompilerSettingsPanelLongText" xml:space="preserve">
<value>The settings selected below are applied to the decompiler output in combination with the selection in the language drop-down. Selecting a lower language version in the drop-down will deactivate all selected options of the higher versions. Note that some settings implicitly depend on each other, e.g.: LINQ expressions cannot be introduced without first transforming static calls to extension method calls.</value>
</data>

Loading…
Cancel
Save