Browse Source

Merge branch 'master' of https://github.com/icsharpcode/ILSpy into ldvirtdelegate

pull/1608/head
Siegfried Pammer 6 years ago
parent
commit
360fd68263
  1. 1
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  2. 6
      ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
  3. 14
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs
  4. 247
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/ThrowExpressions.cs
  5. 10
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/ValueTypes.cs
  6. 13
      ICSharpCode.Decompiler/CSharp/Annotations.cs
  7. 16
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  8. 3
      ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs
  9. 14
      ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
  10. 14
      ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs
  11. 20
      ICSharpCode.Decompiler/DecompilerSettings.cs
  12. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  13. 2
      ICSharpCode.Decompiler/IL/Instructions.cs
  14. 2
      ICSharpCode.Decompiler/IL/Instructions.tt
  15. 2
      ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs
  16. 5
      ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs
  17. 11
      ICSharpCode.Decompiler/IL/Transforms/CopyPropagation.cs
  18. 16
      ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
  19. 82
      ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs
  20. 6
      ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs
  21. 2
      ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs
  22. 12
      ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs
  23. 18
      ICSharpCode.Decompiler/Semantics/Conversion.cs
  24. 29
      ICSharpCode.Decompiler/Semantics/ThrowResolveResult.cs
  25. 2
      ILSpy.BamlDecompiler/Rewrite/MarkupExtensionRewritePass.cs
  26. 9
      ILSpy/Properties/Resources.Designer.cs
  27. 3
      ILSpy/Properties/Resources.resx

1
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -82,6 +82,7 @@
<Compile Include="TestCases\ILPretty\ConstantBlobs.cs" /> <Compile Include="TestCases\ILPretty\ConstantBlobs.cs" />
<Compile Include="TestCases\Pretty\OutVariables.cs" /> <Compile Include="TestCases\Pretty\OutVariables.cs" />
<None Include="TestCases\VBPretty\VBCompoundAssign.cs" /> <None Include="TestCases\VBPretty\VBCompoundAssign.cs" />
<Compile Include="TestCases\Pretty\ThrowExpressions.cs" />
<None Include="TestCases\ILPretty\Issue1145.cs" /> <None Include="TestCases\ILPretty\Issue1145.cs" />
<Compile Include="TestCases\ILPretty\Issue1157.cs" /> <Compile Include="TestCases\ILPretty\Issue1157.cs" />
<None Include="TestCases\ILPretty\Unsafe.cs" /> <None Include="TestCases\ILPretty\Unsafe.cs" />

6
ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

@ -329,6 +329,12 @@ namespace ICSharpCode.Decompiler.Tests
RunForLibrary(cscOptions: cscOptions); RunForLibrary(cscOptions: cscOptions);
} }
[Test]
public void ThrowExpressions([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions)
{
RunForLibrary(cscOptions: cscOptions);
}
[Test] [Test]
public void WellKnownConstants([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) public void WellKnownConstants([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
{ {

14
ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs

@ -25,6 +25,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
private class MyClass private class MyClass
{ {
public int IntVal; public int IntVal;
public readonly int ReadonlyIntVal;
public MyStruct StructField;
public readonly MyStruct ReadonlyStructField;
public string Text; public string Text;
public MyClass Field; public MyClass Field;
public MyClass Property { public MyClass Property {
@ -45,6 +48,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
private struct MyStruct private struct MyStruct
{ {
public int IntVal; public int IntVal;
public readonly int ReadonlyIntVal;
public MyClass Field; public MyClass Field;
public MyStruct? Property1 => null; public MyStruct? Property1 => null;
public MyStruct Property2 => default(MyStruct); public MyStruct Property2 => default(MyStruct);
@ -179,6 +183,16 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
Use(GetMyClass()?.Text ?? "Hello"); Use(GetMyClass()?.Text ?? "Hello");
} }
public void CallOnValueTypeField()
{
Use(GetMyClass()?.IntVal.ToString());
Use(GetMyStruct()?.IntVal.ToString());
Use(GetMyClass()?.ReadonlyIntVal.ToString());
Use(GetMyStruct()?.ReadonlyIntVal.ToString());
GetMyClass()?.StructField.Done();
GetMyClass()?.ReadonlyStructField.Done();
}
public void InvokeDelegate(EventHandler eh) public void InvokeDelegate(EventHandler eh)
{ {
eh?.Invoke(null, EventArgs.Empty); eh?.Invoke(null, EventArgs.Empty);

247
ICSharpCode.Decompiler.Tests/TestCases/Pretty/ThrowExpressions.cs

@ -0,0 +1,247 @@
using System;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
internal class ThrowExpressions
{
private class ArgumentCheckingCtor
{
private int initializedFromCtor = CountSheep() ?? throw new Exception("No sheep?!");
private object cacheObj = TryGetObj() ?? throw new Exception("What?");
private object simpleObj;
private int? nullableInt;
public ArgumentCheckingCtor(object simpleObj, int? nullableInt)
{
this.simpleObj = (simpleObj ?? throw new ArgumentNullException("simpleObj"));
this.nullableInt = (nullableInt ?? throw new ArgumentNullException("nullableInt"));
}
public ArgumentCheckingCtor(string input)
: this(input, GetIntOrNull(input ?? throw new ArgumentNullException("input")))
{
}
public ArgumentCheckingCtor(DataObject obj)
: this(obj ?? throw new Exception(), GetIntOrNull(obj.NullableDataField?.NullableDataField.ToString() ?? throw new ArgumentNullException("input")))
{
}
private static int? GetIntOrNull(string v)
{
if (int.TryParse(v, out int result)) {
return result;
}
return null;
}
private static int? CountSheep()
{
throw new NotImplementedException();
}
private static object TryGetObj()
{
return null;
}
public override int GetHashCode()
{
return initializedFromCtor;
}
public override bool Equals(object obj)
{
return true;
}
}
public class DataObject
{
public int IntField;
public int? NullableIntField;
public Data DataField;
public Data? NullableDataField;
public int IntProperty {
get;
set;
}
public int? NullableIntProperty {
get;
set;
}
public Data DataProperty {
get;
}
public Data? NullableDataProperty {
get;
}
}
public struct Data
{
public int IntField;
public int? NullableIntField;
public MoreData DataField;
public MoreData? NullableDataField;
public int IntProperty {
get;
set;
}
public int? NullableIntProperty {
get;
set;
}
public MoreData DataProperty {
get;
}
public MoreData? NullableDataProperty {
get;
}
}
public struct MoreData
{
public int IntField;
public int? NullableIntField;
public int IntProperty {
get;
set;
}
public int? NullableIntProperty {
get;
set;
}
}
public static int IntField;
public static int? NullableIntField;
public static object ObjectField;
public int InstIntField;
public int? InstNullableIntField;
public object InstObjectField;
public Data DataField;
public Data? NullableDataField;
public DataObject DataObjectField;
public static int IntProperty {
get;
}
public static int? NullableIntProperty {
get;
}
public static object ObjProperty {
get;
}
public int InstIntProperty {
get;
}
public int? InstNullableIntProperty {
get;
}
public object InstObjProperty {
get;
}
public Data DataProperty {
get;
}
public Data? NullableDataProperty {
get;
}
public DataObject DataObjectProperty {
get;
}
public static int ReturnIntField()
{
return NullableIntField ?? throw new Exception();
}
public static int ReturnIntProperty()
{
return NullableIntProperty ?? throw new Exception();
}
public static object ReturnObjField()
{
return ObjectField ?? throw new Exception();
}
public static object ReturnObjProperty()
{
return ObjProperty ?? throw new Exception();
}
public static int ReturnIntField(ThrowExpressions inst)
{
return inst.InstNullableIntField ?? throw new Exception();
}
public static int ReturnIntProperty(ThrowExpressions inst)
{
return inst.InstNullableIntProperty ?? throw new Exception();
}
public static object ReturnObjField(ThrowExpressions inst)
{
return inst.InstObjectField ?? throw new Exception();
}
public static object ReturnObjProperty(ThrowExpressions inst)
{
return inst.InstObjProperty ?? throw new Exception();
}
public static void UseComplexNullableStruct(ThrowExpressions inst)
{
Use(inst.InstNullableIntField ?? throw new Exception());
Use((inst.NullableDataField ?? throw new Exception()).IntField);
Use(inst.NullableDataField?.NullableIntField ?? throw new Exception());
Use((inst.NullableDataProperty ?? throw new Exception()).IntField);
Use(inst.NullableDataProperty?.NullableIntField ?? throw new Exception());
Use((inst.NullableDataField ?? throw new Exception()).DataField.IntField);
Use(inst.NullableDataField?.DataField.NullableIntField ?? throw new Exception());
Use((inst.NullableDataProperty ?? throw new Exception()).DataField.IntField);
Use(inst.NullableDataProperty?.DataField.NullableIntField ?? throw new Exception());
Use((inst.NullableDataField ?? throw new Exception()).DataProperty.IntField);
Use(inst.NullableDataField?.DataProperty.NullableIntField ?? throw new Exception());
Use((inst.NullableDataProperty ?? throw new Exception()).DataProperty.IntField);
Use(inst.NullableDataProperty?.DataProperty.NullableIntField ?? throw new Exception());
Use(inst.NullableDataField?.NullableDataField?.IntField ?? throw new Exception());
Use(inst.NullableDataField?.NullableDataField?.NullableIntField ?? throw new Exception());
Use(inst.NullableDataProperty?.NullableDataField?.IntField ?? throw new Exception());
Use(inst.NullableDataProperty?.NullableDataField?.NullableIntField ?? throw new Exception());
Use(inst.NullableDataField?.NullableDataProperty?.IntField ?? throw new Exception());
Use(inst.NullableDataField?.NullableDataProperty?.NullableIntField ?? throw new Exception());
Use(inst.NullableDataProperty?.NullableDataProperty?.IntField ?? throw new Exception());
Use(inst.NullableDataProperty?.NullableDataProperty?.NullableIntField ?? throw new Exception());
}
public static void UseComplexNullableObject(DataObject inst)
{
Use(inst?.NullableIntField ?? throw new Exception());
Use(inst?.NullableDataField?.IntField ?? throw new Exception());
Use(inst?.NullableDataField?.NullableIntField ?? throw new Exception());
Use(inst?.NullableDataProperty?.IntField ?? throw new Exception());
Use(inst?.NullableDataProperty?.NullableIntField ?? throw new Exception());
Use(inst?.NullableDataField?.DataField.IntField ?? throw new Exception());
Use(inst?.NullableDataField?.DataField.NullableIntField ?? throw new Exception());
Use(inst?.NullableDataProperty?.DataField.IntField ?? throw new Exception());
Use(inst?.NullableDataProperty?.DataField.NullableIntField ?? throw new Exception());
Use(inst?.NullableDataField?.DataProperty.IntField ?? throw new Exception());
Use(inst?.NullableDataField?.DataProperty.NullableIntField ?? throw new Exception());
Use(inst?.NullableDataProperty?.DataProperty.IntField ?? throw new Exception());
Use(inst?.NullableDataProperty?.DataProperty.NullableIntField ?? throw new Exception());
Use(inst?.NullableDataField?.NullableDataField?.IntField ?? throw new Exception());
Use(inst?.NullableDataField?.NullableDataField?.NullableIntField ?? throw new Exception());
Use(inst?.NullableDataProperty?.NullableDataField?.IntField ?? throw new Exception());
Use(inst?.NullableDataProperty?.NullableDataField?.NullableIntField ?? throw new Exception());
Use(inst?.NullableDataField?.NullableDataProperty?.IntField ?? throw new Exception());
Use(inst?.NullableDataField?.NullableDataProperty?.NullableIntField ?? throw new Exception());
Use(inst?.NullableDataProperty?.NullableDataProperty?.IntField ?? throw new Exception());
Use(inst?.NullableDataProperty?.NullableDataProperty?.NullableIntField ?? throw new Exception());
}
public static void Use<T>(T usage)
{
}
}
}

10
ICSharpCode.Decompiler.Tests/TestCases/Pretty/ValueTypes.cs

@ -66,6 +66,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
S s = this; S s = this;
s.SetField(); s.SetField();
} }
public void UseField(int val)
{
UseField(Get<S>().Field);
}
} }
#if CS72 #if CS72
@ -263,5 +268,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
r.Property = 2; r.Property = 2;
#endif #endif
} }
public static void CallOnFieldOfTemporary()
{
Get<S>().Field.ToString();
}
} }
} }

13
ICSharpCode.Decompiler/CSharp/Annotations.cs

@ -19,6 +19,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using ICSharpCode.Decompiler.CSharp.Resolver;
using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.Syntax;
using ICSharpCode.Decompiler.IL; using ICSharpCode.Decompiler.IL;
using ICSharpCode.Decompiler.Semantics; using ICSharpCode.Decompiler.Semantics;
@ -104,7 +105,17 @@ namespace ICSharpCode.Decompiler.CSharp
public static ISymbol GetSymbol(this AstNode node) public static ISymbol GetSymbol(this AstNode node)
{ {
var rr = node.Annotation<ResolveResult>(); var rr = node.Annotation<ResolveResult>();
return rr != null ? rr.GetSymbol() : null; if (rr is MethodGroupResolveResult) {
// delegate construction?
var newObj = node.Annotation<NewObj>();
var funcptr = newObj?.Arguments.ElementAtOrDefault(1);
if (funcptr is LdFtn ldftn) {
return ldftn.Method;
} else if (funcptr is LdVirtFtn ldVirtFtn) {
return ldVirtFtn.Method;
}
}
return rr?.GetSymbol();
} }
/// <summary> /// <summary>

16
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -134,7 +134,7 @@ namespace ICSharpCode.Decompiler.CSharp
}; };
var cexpr = inst.AcceptVisitor(this, context); var cexpr = inst.AcceptVisitor(this, context);
#if DEBUG #if DEBUG
if (inst.ResultType != StackType.Void && cexpr.Type.Kind != TypeKind.Unknown && inst.ResultType != StackType.Unknown) { if (inst.ResultType != StackType.Void && cexpr.Type.Kind != TypeKind.Unknown && inst.ResultType != StackType.Unknown && cexpr.Type.Kind != TypeKind.None) {
// Validate the Translate post-condition (documented at beginning of this file): // Validate the Translate post-condition (documented at beginning of this file):
if (inst.ResultType.IsIntegerType()) { if (inst.ResultType.IsIntegerType()) {
Debug.Assert(cexpr.Type.GetStackType().IsIntegerType(), "IL instructions of integer type must convert into C# expressions of integer type"); Debug.Assert(cexpr.Type.GetStackType().IsIntegerType(), "IL instructions of integer type must convert into C# expressions of integer type");
@ -949,6 +949,13 @@ namespace ICSharpCode.Decompiler.CSharp
.WithILInstruction(inst); .WithILInstruction(inst);
} }
protected internal override TranslatedExpression VisitThrow(Throw inst, TranslationContext context)
{
return new ThrowExpression(Translate(inst.Argument))
.WithILInstruction(inst)
.WithRR(new ThrowResolveResult());
}
protected internal override TranslatedExpression VisitUserDefinedLogicOperator(UserDefinedLogicOperator inst, TranslationContext context) protected internal override TranslatedExpression VisitUserDefinedLogicOperator(UserDefinedLogicOperator inst, TranslationContext context)
{ {
var left = Translate(inst.Left, inst.Method.Parameters[0].Type).ConvertTo(inst.Method.Parameters[0].Type, this); var left = Translate(inst.Left, inst.Method.Parameters[0].Type).ConvertTo(inst.Method.Parameters[0].Type, this);
@ -2185,8 +2192,7 @@ namespace ICSharpCode.Decompiler.CSharp
// try via its effective base class. // try via its effective base class.
arg = arg.ConvertTo(((ITypeParameter)targetType).EffectiveBaseClass, this); arg = arg.ConvertTo(((ITypeParameter)targetType).EffectiveBaseClass, this);
} }
} } else {
else {
// Before unboxing arg must be a object // Before unboxing arg must be a object
arg = arg.ConvertTo(compilation.FindType(KnownTypeCode.Object), this); arg = arg.ConvertTo(compilation.FindType(KnownTypeCode.Object), this);
} }
@ -2658,7 +2664,9 @@ namespace ICSharpCode.Decompiler.CSharp
var rr = resolver.ResolveBinaryOperator(BinaryOperatorType.NullCoalescing, value.ResolveResult, fallback.ResolveResult); var rr = resolver.ResolveBinaryOperator(BinaryOperatorType.NullCoalescing, value.ResolveResult, fallback.ResolveResult);
if (rr.IsError) { if (rr.IsError) {
IType targetType; IType targetType;
if (!value.Type.Equals(SpecialType.NullType) && !fallback.Type.Equals(SpecialType.NullType) && !value.Type.Equals(fallback.Type)) { if (fallback.Expression is ThrowExpression && fallback.Type.Equals(SpecialType.NoType)) {
targetType = NullableType.GetUnderlyingType(value.Type);
} else if (!value.Type.Equals(SpecialType.NullType) && !fallback.Type.Equals(SpecialType.NullType) && !value.Type.Equals(fallback.Type)) {
targetType = compilation.FindType(inst.UnderlyingResultType.ToKnownTypeCode()); targetType = compilation.FindType(inst.UnderlyingResultType.ToKnownTypeCode());
} else { } else {
targetType = value.Type.Equals(SpecialType.NullType) ? fallback.Type : value.Type; targetType = value.Type.Equals(SpecialType.NullType) ? fallback.Type : value.Type;

3
ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs

@ -115,6 +115,9 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
if (c != Conversion.None) if (c != Conversion.None)
return c; return c;
} }
if (resolveResult is ThrowResolveResult) {
return Conversion.ThrowExpressionConversion;
}
if (allowUserDefined && allowTuple) { if (allowUserDefined && allowTuple) {
// if allowUserDefined and allowTuple are true, we might as well use the cache // if allowUserDefined and allowTuple are true, we might as well use the cache
c = ImplicitConversion(resolveResult.Type, toType); c = ImplicitConversion(resolveResult.Type, toType);

14
ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs

@ -527,12 +527,18 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
{ {
Attribute attr = new Attribute(); Attribute attr = new Attribute();
attr.Type = ConvertAttributeType(attribute.AttributeType); attr.Type = ConvertAttributeType(attribute.AttributeType);
SimpleType st = attr.Type as SimpleType; switch (attr.Type) {
MemberType mt = attr.Type as MemberType; case SimpleType st:
if (st != null && st.Identifier.EndsWith("Attribute", StringComparison.Ordinal)) { if (st.Identifier.EndsWith("Attribute", StringComparison.Ordinal))
st.Identifier = st.Identifier.Substring(0, st.Identifier.Length - 9); st.Identifier = st.Identifier.Substring(0, st.Identifier.Length - 9);
} else if (mt != null && mt.MemberName.EndsWith("Attribute", StringComparison.Ordinal)) { break;
case MemberType mt:
if (mt.MemberName.EndsWith("Attribute", StringComparison.Ordinal))
mt.MemberName = mt.MemberName.Substring(0, mt.MemberName.Length - 9); mt.MemberName = mt.MemberName.Substring(0, mt.MemberName.Length - 9);
break;
}
if (AddResolveResultAnnotations && attribute.Constructor != null) {
attr.AddAnnotation(new MemberResolveResult(null, attribute.Constructor));
} }
var parameters = attribute.Constructor?.Parameters ?? EmptyList<IParameter>.Instance; var parameters = attribute.Constructor?.Parameters ?? EmptyList<IParameter>.Instance;
for (int i = 0; i < attribute.FixedArguments.Length; i++) { for (int i = 0; i < attribute.FixedArguments.Length; i++) {

14
ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs

@ -176,6 +176,17 @@ namespace ICSharpCode.Decompiler.CSharp
/// ///
/// From the caller's perspective, IntPtr/UIntPtr behave like normal C# integers except that they have native int size. /// From the caller's perspective, IntPtr/UIntPtr behave like normal C# integers except that they have native int size.
/// All the special cases necessary to make IntPtr/UIntPtr behave sanely are handled internally in ConvertTo(). /// All the special cases necessary to make IntPtr/UIntPtr behave sanely are handled internally in ConvertTo().
///
/// Post-condition:
/// The "expected evaluation result" is the value computed by <c>this.Expression</c>,
/// converted to targetType via an IL conv instruction.
///
/// ConvertTo(targetType, allowImplicitConversion=false).Type must be equal to targetType (modulo identity conversions).
/// The value computed by the converted expression must match the "expected evaluation result".
///
/// ConvertTo(targetType, allowImplicitConversion=true) must produce an expression that,
/// when evaluated in a context where it will be implicitly converted to targetType,
/// evaluates to the "expected evaluation result".
/// </remarks> /// </remarks>
public TranslatedExpression ConvertTo(IType targetType, ExpressionBuilder expressionBuilder, bool checkForOverflow = false, bool allowImplicitConversion = false) public TranslatedExpression ConvertTo(IType targetType, ExpressionBuilder expressionBuilder, bool checkForOverflow = false, bool allowImplicitConversion = false)
{ {
@ -227,6 +238,9 @@ namespace ICSharpCode.Decompiler.CSharp
.WithRR(convAnnotation.ConversionResolveResult) .WithRR(convAnnotation.ConversionResolveResult)
.ConvertTo(targetType, expressionBuilder, checkForOverflow, allowImplicitConversion); .ConvertTo(targetType, expressionBuilder, checkForOverflow, allowImplicitConversion);
} }
if (Expression is ThrowExpression && allowImplicitConversion) {
return this; // Throw expressions have no type and are implicitly convertible to any type
}
if (Expression is TupleExpression tupleExpr && targetType is TupleType targetTupleType if (Expression is TupleExpression tupleExpr && targetType is TupleType targetTupleType
&& tupleExpr.Elements.Count == targetTupleType.ElementTypes.Length) && tupleExpr.Elements.Count == targetTupleType.ElementTypes.Length)
{ {

20
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -88,6 +88,7 @@ namespace ICSharpCode.Decompiler
} }
if (languageVersion < CSharp.LanguageVersion.CSharp7) { if (languageVersion < CSharp.LanguageVersion.CSharp7) {
outVariables = false; outVariables = false;
throwExpressions = false;
tupleTypes = false; tupleTypes = false;
tupleConversions = false; tupleConversions = false;
discards = false; discards = false;
@ -118,7 +119,7 @@ namespace ICSharpCode.Decompiler
if (introduceRefModifiersOnStructs || introduceReadonlyAndInModifiers || nonTrailingNamedArguments || refExtensionMethods) if (introduceRefModifiersOnStructs || introduceReadonlyAndInModifiers || nonTrailingNamedArguments || refExtensionMethods)
return CSharp.LanguageVersion.CSharp7_2; return CSharp.LanguageVersion.CSharp7_2;
// C# 7.1 missing // C# 7.1 missing
if (outVariables || tupleTypes || tupleConversions || discards || localFunctions) if (outVariables || throwExpressions || tupleTypes || tupleConversions || discards || localFunctions)
return CSharp.LanguageVersion.CSharp7; return CSharp.LanguageVersion.CSharp7;
if (awaitInCatchFinally || useExpressionBodyForCalculatedGetterOnlyProperties || nullPropagation if (awaitInCatchFinally || useExpressionBodyForCalculatedGetterOnlyProperties || nullPropagation
|| stringInterpolation || dictionaryInitializers || extensionMethodsInCollectionInitializers) || stringInterpolation || dictionaryInitializers || extensionMethodsInCollectionInitializers)
@ -874,6 +875,23 @@ namespace ICSharpCode.Decompiler
} }
} }
bool throwExpressions = true;
/// <summary>
/// Gets/Sets whether throw expressions should be used.
/// </summary>
[Category("C# 7.0 / VS 2017")]
[Description("DecompilerSettings.UseThrowExpressions")]
public bool ThrowExpressions {
get { return throwExpressions; }
set {
if (throwExpressions != value) {
throwExpressions = value;
OnPropertyChanged();
}
}
}
bool tupleConversions = true; bool tupleConversions = true;
/// <summary> /// <summary>

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -384,6 +384,7 @@
<Compile Include="TypeSystem\ModifiedType.cs" /> <Compile Include="TypeSystem\ModifiedType.cs" />
<Compile Include="TypeSystem\Implementation\PinnedType.cs" /> <Compile Include="TypeSystem\Implementation\PinnedType.cs" />
<Compile Include="Metadata\MetadataExtensions.cs" /> <Compile Include="Metadata\MetadataExtensions.cs" />
<Compile Include="Semantics\ThrowResolveResult.cs" />
<Compile Include="Semantics\TupleResolveResult.cs" /> <Compile Include="Semantics\TupleResolveResult.cs" />
<Compile Include="TypeSystem\NormalizeTypeVisitor.cs" /> <Compile Include="TypeSystem\NormalizeTypeVisitor.cs" />
<Compile Include="TypeSystem\Nullability.cs" /> <Compile Include="TypeSystem\Nullability.cs" />

2
ICSharpCode.Decompiler/IL/Instructions.cs

@ -4432,7 +4432,7 @@ namespace ICSharpCode.Decompiler.IL
public Throw(ILInstruction argument) : base(OpCode.Throw, argument) public Throw(ILInstruction argument) : base(OpCode.Throw, argument)
{ {
} }
public override StackType ResultType { get { return StackType.Void; } } public override StackType ResultType { get { return this.resultType; } }
protected override InstructionFlags ComputeFlags() protected override InstructionFlags ComputeFlags()
{ {
return base.ComputeFlags() | InstructionFlags.MayThrow | InstructionFlags.EndPointUnreachable; return base.ComputeFlags() | InstructionFlags.MayThrow | InstructionFlags.EndPointUnreachable;

2
ICSharpCode.Decompiler/IL/Instructions.tt

@ -268,7 +268,7 @@
new OpCode("default.value", "Returns the default value for a type.", new OpCode("default.value", "Returns the default value for a type.",
NoArguments, HasTypeOperand, ResultType("type.GetStackType()")), NoArguments, HasTypeOperand, ResultType("type.GetStackType()")),
new OpCode("throw", "Throws an exception.", new OpCode("throw", "Throws an exception.",
Unary, MayThrow, UnconditionalBranch), Unary, MayThrow, HasFlag("InstructionFlags.EndPointUnreachable"), ResultType("this.resultType")),
new OpCode("rethrow", "Rethrows the current exception.", new OpCode("rethrow", "Rethrows the current exception.",
NoArguments, MayThrow, UnconditionalBranch), NoArguments, MayThrow, UnconditionalBranch),
new OpCode("sizeof", "Gets the size of a type in bytes.", new OpCode("sizeof", "Gets the size of a type in bytes.",

2
ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs

@ -308,7 +308,7 @@ namespace ICSharpCode.Decompiler.IL
protected abstract SlotInfo GetChildSlot(int index); protected abstract SlotInfo GetChildSlot(int index);
#region ChildrenCollection + ChildrenEnumerator #region ChildrenCollection + ChildrenEnumerator
public struct ChildrenCollection : IReadOnlyList<ILInstruction> public readonly struct ChildrenCollection : IReadOnlyList<ILInstruction>
{ {
readonly ILInstruction inst; readonly ILInstruction inst;

5
ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs

@ -362,4 +362,9 @@ namespace ICSharpCode.Decompiler.IL
} }
} }
} }
public partial class Throw
{
internal StackType resultType = StackType.Void;
}
} }

11
ICSharpCode.Decompiler/IL/Transforms/CopyPropagation.cs

@ -105,13 +105,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// Parameters can be copied only if they aren't assigned to (directly or indirectly via ldarga) // Parameters can be copied only if they aren't assigned to (directly or indirectly via ldarga)
// note: the initialization by the caller is the first store -> StoreCount must be 1 // note: the initialization by the caller is the first store -> StoreCount must be 1
return v.IsSingleDefinition; return v.IsSingleDefinition;
case VariableKind.StackSlot:
case VariableKind.ExceptionStackSlot:
// Variables are be copied only if both they and the target copy variable are generated,
// and if the variable has only a single assignment
return v.IsSingleDefinition && target.Kind == VariableKind.StackSlot;
default: default:
return false; // Variables can be copied if both are single-definition.
// To avoid removing too many variables, we do this only if the target
// is either a stackslot or a ref local.
Debug.Assert(target.IsSingleDefinition);
return v.IsSingleDefinition && (target.Kind == VariableKind.StackSlot || target.StackType == StackType.Ref);
} }
default: default:
// All instructions without special behavior that target a stack-variable can be copied. // All instructions without special behavior that target a stack-variable can be copied.

16
ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs

@ -272,10 +272,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false; return false;
if (ldloca.Variable.Type.IsReferenceType ?? false) if (ldloca.Variable.Type.IsReferenceType ?? false)
return false; return false;
switch (ldloca.Parent.OpCode) { ILInstruction inst = ldloca;
while (inst.Parent is LdFlda ldflda) {
inst = ldflda;
}
switch (inst.Parent.OpCode) {
case OpCode.Call: case OpCode.Call:
case OpCode.CallVirt: case OpCode.CallVirt:
var method = ((CallInstruction)ldloca.Parent).Method; var method = ((CallInstruction)inst.Parent).Method;
if (method.IsAccessor && method.AccessorKind != MethodSemanticsAttributes.Getter) { if (method.IsAccessor && method.AccessorKind != MethodSemanticsAttributes.Getter) {
// C# doesn't allow calling setters on temporary structs // C# doesn't allow calling setters on temporary structs
return false; return false;
@ -284,7 +288,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
case OpCode.Await: case OpCode.Await:
return true; return true;
case OpCode.NullableUnwrap: case OpCode.NullableUnwrap:
return ((NullableUnwrap)ldloca.Parent).RefInput; return ((NullableUnwrap)inst.Parent).RefInput;
default: default:
return false; return false;
} }
@ -346,13 +350,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{ {
switch (addr) { switch (addr) {
case LdFlda ldflda: case LdFlda ldflda:
return ldflda.Field.IsReadOnly; return ldflda.Field.IsReadOnly
|| (ldflda.Field.DeclaringType.Kind == TypeKind.Struct && IsReadonlyReference(ldflda.Target));
case LdsFlda ldsflda: case LdsFlda ldsflda:
return ldsflda.Field.IsReadOnly; return ldsflda.Field.IsReadOnly;
case LdLoc ldloc: case LdLoc ldloc:
return IsReadonlyRefLocal(ldloc.Variable); return IsReadonlyRefLocal(ldloc.Variable);
case Call call: case Call call:
return call.Method.ReturnTypeIsRefReadOnly; return call.Method.ReturnTypeIsRefReadOnly;
case AddressOf _:
// C# doesn't allow mutation of value-type temporaries
return true;
default: default:
return false; return false;
} }

82
ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs

@ -21,6 +21,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using ICSharpCode.Decompiler.TypeSystem;
namespace ICSharpCode.Decompiler.IL.Transforms namespace ICSharpCode.Decompiler.IL.Transforms
{ {
@ -34,18 +35,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{ {
public void Run(Block block, int pos, StatementTransformContext context) public void Run(Block block, int pos, StatementTransformContext context)
{ {
TransformRefTypes(block, pos, context); if (!TransformRefTypes(block, pos, context)) {
TransformThrowExpressionValueTypes(block, pos, context);
}
} }
/// <summary> /// <summary>
/// Handles NullCoalescingInstruction case 1: reference types. /// Handles NullCoalescingInstruction case 1: reference types.
///
/// stloc s(valueInst)
/// if (comp(ldloc s == ldnull)) {
/// stloc s(fallbackInst)
/// }
/// =>
/// stloc s(if.notnull(valueInst, fallbackInst))
/// </summary> /// </summary>
bool TransformRefTypes(Block block, int pos, StatementTransformContext context) bool TransformRefTypes(Block block, int pos, StatementTransformContext context)
{ {
@ -58,6 +54,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (!(condition.MatchCompEquals(out var left, out var right) && left.MatchLdLoc(stloc.Variable) && right.MatchLdNull())) if (!(condition.MatchCompEquals(out var left, out var right) && left.MatchLdLoc(stloc.Variable) && right.MatchLdNull()))
return false; return false;
trueInst = Block.Unwrap(trueInst); trueInst = Block.Unwrap(trueInst);
// stloc s(valueInst)
// if (comp(ldloc s == ldnull)) {
// stloc s(fallbackInst)
// }
// =>
// stloc s(if.notnull(valueInst, fallbackInst))
if (trueInst.MatchStLoc(stloc.Variable, out var fallbackValue)) { if (trueInst.MatchStLoc(stloc.Variable, out var fallbackValue)) {
context.Step("NullCoalescingTransform: simple (reference types)", stloc); context.Step("NullCoalescingTransform: simple (reference types)", stloc);
stloc.Value = new NullCoalescingInstruction(NullCoalescingKind.Ref, stloc.Value, fallbackValue); stloc.Value = new NullCoalescingInstruction(NullCoalescingKind.Ref, stloc.Value, fallbackValue);
@ -83,7 +85,71 @@ namespace ICSharpCode.Decompiler.IL.Transforms
ILInlining.InlineOneIfPossible(block, pos, InliningOptions.None, context); ILInlining.InlineOneIfPossible(block, pos, InliningOptions.None, context);
return true; return true;
} }
// stloc obj(valueInst)
// if (comp(ldloc obj == ldnull)) {
// throw(...)
// }
// =>
// stloc obj(if.notnull(valueInst, throw(...)))
if (context.Settings.ThrowExpressions && trueInst is Throw throwInst) {
context.Step("NullCoalescingTransform (reference types + throw expression)", stloc);
throwInst.resultType = StackType.O;
stloc.Value = new NullCoalescingInstruction(NullCoalescingKind.Ref, stloc.Value, throwInst);
block.Instructions.RemoveAt(pos + 1); // remove if instruction
ILInlining.InlineOneIfPossible(block, pos, InliningOptions.None, context);
return true;
}
return false;
}
/// <summary>
/// stloc v(value)
/// if (logic.not(call get_HasValue(ldloca v))) throw(...)
/// ... Call(arg1, arg2, call GetValueOrDefault(ldloca v), arg4) ...
/// =>
/// ... Call(arg1, arg2, if.notnull(value, throw(...)), arg4) ...
/// </summary>
bool TransformThrowExpressionValueTypes(Block block, int pos, StatementTransformContext context)
{
if (pos + 2 >= block.Instructions.Count)
return false;
if (!(block.Instructions[pos] is StLoc stloc))
return false;
ILVariable v = stloc.Variable;
if (!(v.StoreCount == 1 && v.LoadCount == 0 && v.AddressCount == 2))
return false;
if (!block.Instructions[pos + 1].MatchIfInstruction(out var condition, out var trueInst))
return false; return false;
if (!(Block.Unwrap(trueInst) is Throw throwInst))
return false;
if (!condition.MatchLogicNot(out var arg))
return false;
if (!(arg is CallInstruction call && NullableLiftingTransform.MatchHasValueCall(call, v)))
return false;
var throwInstParent = throwInst.Parent;
var throwInstChildIndex = throwInst.ChildIndex;
var nullCoalescingWithThrow = new NullCoalescingInstruction(
NullCoalescingKind.NullableWithValueFallback,
stloc.Value,
throwInst);
var resultType = NullableType.GetUnderlyingType(call.Method.DeclaringType).GetStackType();
nullCoalescingWithThrow.UnderlyingResultType = resultType;
var result = ILInlining.FindLoadInNext(block.Instructions[pos + 2], v, nullCoalescingWithThrow, InliningOptions.None);
if (result.Type == ILInlining.FindResultType.Found
&& NullableLiftingTransform.MatchGetValueOrDefault(result.LoadInst.Parent, v))
{
context.Step("NullCoalescingTransform (value types + throw expression)", stloc);
throwInst.resultType = resultType;
result.LoadInst.Parent.ReplaceWith(nullCoalescingWithThrow);
block.Instructions.RemoveRange(pos, 2); // remove store(s) and if instruction
return true;
} else {
// reset the primary position (see remarks on ILInstruction.Parent)
stloc.Value = stloc.Value;
var children = throwInstParent.Children;
children[throwInstChildIndex] = throwInst;
return false;
}
} }
} }
} }

6
ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs

@ -186,6 +186,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return chainLength >= 1; return chainLength >= 1;
} else if (inst.MatchLdFld(out var target, out _)) { } else if (inst.MatchLdFld(out var target, out _)) {
inst = target; inst = target;
} else if (inst.MatchLdFlda(out target, out var f)) {
if (target is AddressOf addressOf && f.DeclaringType.Kind == TypeKind.Struct) {
inst = addressOf.Value;
} else {
inst = target;
}
} else if (inst is CallInstruction call && call.OpCode != OpCode.NewObj) { } else if (inst is CallInstruction call && call.OpCode != OpCode.NewObj) {
if (call.Arguments.Count == 0) { if (call.Arguments.Count == 0) {
return false; return false;

2
ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs

@ -833,7 +833,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// <summary> /// <summary>
/// Matches 'logic.not(call get_HasValue(ldloca v))' /// Matches 'logic.not(call get_HasValue(ldloca v))'
/// </summary> /// </summary>
static bool MatchNegatedHasValueCall(ILInstruction inst, ILVariable v) internal static bool MatchNegatedHasValueCall(ILInstruction inst, ILVariable v)
{ {
return inst.MatchLogicNot(out var arg) && MatchHasValueCall(arg, v); return inst.MatchLogicNot(out var arg) && MatchHasValueCall(arg, v);
} }

12
ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs

@ -109,7 +109,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// Address stored in local variable: also check all uses of that variable. // Address stored in local variable: also check all uses of that variable.
if (!(stloc.Variable.Kind == VariableKind.StackSlot || stloc.Variable.Kind == VariableKind.Local)) if (!(stloc.Variable.Kind == VariableKind.StackSlot || stloc.Variable.Kind == VariableKind.Local))
return AddressUse.Unknown; return AddressUse.Unknown;
if (stloc.Value.OpCode != OpCode.LdLoca) { var value = stloc.Value;
while (value is LdFlda ldFlda) {
value = ldFlda.Target;
}
if (value.OpCode != OpCode.LdLoca) {
// GroupStores.HandleLoad() only detects ref-locals when they are directly initialized with ldloca // GroupStores.HandleLoad() only detects ref-locals when they are directly initialized with ldloca
return AddressUse.Unknown; return AddressUse.Unknown;
} }
@ -162,7 +166,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return null; // only single-definition variables can be supported ref locals return null; // only single-definition variables can be supported ref locals
var store = ldloc.Variable.StoreInstructions.SingleOrDefault(); var store = ldloc.Variable.StoreInstructions.SingleOrDefault();
if (store is StLoc stloc) { if (store is StLoc stloc) {
return stloc.Value as LdLoca; var value = stloc.Value;
while (value is LdFlda ldFlda) {
value = ldFlda.Target;
}
return value as LdLoca;
} }
return null; return null;
} }

18
ICSharpCode.Decompiler/Semantics/Conversion.cs

@ -76,8 +76,16 @@ namespace ICSharpCode.Decompiler.Semantics
/// </summary> /// </summary>
public static readonly Conversion TryCast = new BuiltinConversion(false, 9); public static readonly Conversion TryCast = new BuiltinConversion(false, 9);
/// <summary>
/// C# 6 string interpolation expression implicitly being converted to <see cref="System.IFormattable"/> or <see cref="System.FormattableString"/>.
/// </summary>
public static readonly Conversion ImplicitInterpolatedStringConversion = new BuiltinConversion(true, 10); public static readonly Conversion ImplicitInterpolatedStringConversion = new BuiltinConversion(true, 10);
/// <summary>
/// C# 7 throw expression being converted to an arbitrary type.
/// </summary>
public static readonly Conversion ThrowExpressionConversion = new BuiltinConversion(true, 11);
public static Conversion UserDefinedConversion(IMethod operatorMethod, bool isImplicit, Conversion conversionBeforeUserDefinedOperator, Conversion conversionAfterUserDefinedOperator, bool isLifted = false, bool isAmbiguous = false) public static Conversion UserDefinedConversion(IMethod operatorMethod, bool isImplicit, Conversion conversionBeforeUserDefinedOperator, Conversion conversionAfterUserDefinedOperator, bool isLifted = false, bool isAmbiguous = false)
{ {
if (operatorMethod == null) if (operatorMethod == null)
@ -232,6 +240,10 @@ namespace ICSharpCode.Decompiler.Semantics
public override bool IsInterpolatedStringConversion => type == 10; public override bool IsInterpolatedStringConversion => type == 10;
public override bool IsThrowExpressionConversion {
get { return type == 11; }
}
public override string ToString() public override string ToString()
{ {
string name = null; string name = null;
@ -263,6 +275,8 @@ namespace ICSharpCode.Decompiler.Semantics
return "try cast"; return "try cast";
case 10: case 10:
return "interpolated string"; return "interpolated string";
case 11:
return "throw-expression conversion";
} }
return (isImplicit ? "implicit " : "explicit ") + name + " conversion"; return (isImplicit ? "implicit " : "explicit ") + name + " conversion";
} }
@ -450,6 +464,10 @@ namespace ICSharpCode.Decompiler.Semantics
get { return false; } get { return false; }
} }
public virtual bool IsThrowExpressionConversion {
get { return false; }
}
public virtual bool IsIdentityConversion { public virtual bool IsIdentityConversion {
get { return false; } get { return false; }
} }

29
ICSharpCode.Decompiler/Semantics/ThrowResolveResult.cs

@ -0,0 +1,29 @@
// Copyright (c) 2018 Daniel Grunwald
//
// 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.TypeSystem;
namespace ICSharpCode.Decompiler.Semantics
{
class ThrowResolveResult : ResolveResult
{
public ThrowResolveResult() : base(SpecialType.NoType)
{
}
}
}

2
ILSpy.BamlDecompiler/Rewrite/MarkupExtensionRewritePass.cs

@ -77,6 +77,7 @@ namespace ILSpy.BamlDecompiler.Rewrite {
var attrName = elem.Name; var attrName = elem.Name;
if (attrName != key) if (attrName != key)
attrName = property.ToXName(ctx, parent, property.IsAttachedTo(type)); attrName = property.ToXName(ctx, parent, property.IsAttachedTo(type));
if (!parent.Attributes(attrName).Any()) {
var attr = new XAttribute(attrName, extValue); var attr = new XAttribute(attrName, extValue);
var list = new List<XAttribute>(parent.Attributes()); var list = new List<XAttribute>(parent.Attributes());
if (attrName == key) if (attrName == key)
@ -85,6 +86,7 @@ namespace ILSpy.BamlDecompiler.Rewrite {
list.Add(attr); list.Add(attr);
parent.RemoveAttributes(); parent.RemoveAttributes();
parent.ReplaceAttributes(list); parent.ReplaceAttributes(list);
}
elem.Remove(); elem.Remove();
return true; return true;

9
ILSpy/Properties/Resources.Designer.cs generated

@ -935,6 +935,15 @@ namespace ICSharpCode.ILSpy.Properties {
} }
} }
/// <summary>
/// Looks up a localized string similar to Use throw expressions.
/// </summary>
public static string DecompilerSettings_UseThrowExpressions {
get {
return ResourceManager.GetString("DecompilerSettings.UseThrowExpressions", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Use tuple type syntax. /// Looks up a localized string similar to Use tuple type syntax.
/// </summary> /// </summary>

3
ILSpy/Properties/Resources.resx

@ -729,6 +729,9 @@
<data name="CannotAnalyzeMissingRef" xml:space="preserve"> <data name="CannotAnalyzeMissingRef" xml:space="preserve">
<value>Entity could not be resolved. Cannot analyze entities from missing assembly references. Add the missing reference and try again.</value> <value>Entity could not be resolved. Cannot analyze entities from missing assembly references. Add the missing reference and try again.</value>
</data> </data>
<data name="DecompilerSettings.UseThrowExpressions" xml:space="preserve">
<value>Use throw expressions</value>
</data>
<data name="DecompilerSettings.AllowExtensionMethodSyntaxOnRef" xml:space="preserve"> <data name="DecompilerSettings.AllowExtensionMethodSyntaxOnRef" xml:space="preserve">
<value>Use 'ref' extension methods</value> <value>Use 'ref' extension methods</value>
</data> </data>

Loading…
Cancel
Save