diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 52ad5e804..fabc14eba 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -9,7 +9,7 @@ True - 1701;1702;1705,67,169,1058,728,1720,649,168,251,660,661,675;1998;162;8632 + 1701;1702;1705,67,169,1058,728,1720,649,168,251,660,661,675;1998;162;8632;626;8618;8714;8602 False False @@ -33,11 +33,11 @@ - TRACE;DEBUG;ROSLYN;CS60;CS70;CS71;CS72;CS73;CS80;CS90;CS100 + TRACE;DEBUG;ROSLYN;NET60;CS60;CS70;CS71;CS72;CS73;CS80;CS90;CS100 - TRACE;ROSLYN;CS60;CS70;CS71;CS72;CS73;CS80;CS90;CS100 + TRACE;ROSLYN;NET60;CS60;CS70;CS71;CS72;CS73;CS80;CS90;CS100 @@ -220,6 +220,7 @@ + diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index c4027fc39..c0f5e3763 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -538,6 +538,12 @@ namespace ICSharpCode.Decompiler.Tests await RunForLibrary(cscOptions: cscOptions); } + [Test] + public async Task RefFields([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + [Test] public async Task ThrowExpressions([ValueSource(nameof(roslyn2OrNewerOptions))] CompilerOptions cscOptions) { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs index d27914c88..d4d87d49e 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs @@ -1,5 +1,5 @@ using System; -#if ROSLYN4 +#if CS100 using System.Runtime.InteropServices; #endif @@ -242,6 +242,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } #endif } +#if !NET60 namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] @@ -261,3 +262,4 @@ namespace System.Runtime.CompilerServices { } } +#endif diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefFields.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefFields.cs new file mode 100644 index 000000000..55c837ef6 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefFields.cs @@ -0,0 +1,36 @@ +using System; + +namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty +{ + internal ref struct RefFields + { + private ref int Field0; + private ref readonly int Field1; + private readonly ref int Field2; + private readonly ref readonly int Field3; + + public int PropertyAccessingRefFieldByValue { + get { + return Field0; + } + set { + Field0 = value; + } + } + + public ref int PropertyReturningRefFieldByReference => ref Field0; + + public void Uses(int[] array) + { + Field1 = ref array[0]; + Field2 = array[0]; + } + + public void ReadonlyLocal() + { + ref readonly int field = ref Field1; + Console.WriteLine("No inlining"); + field.ToString(); + } + } +} \ No newline at end of file diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index fb8f21a6f..b377aa0d4 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -1970,6 +1970,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax decl.AddAnnotation(new MemberResolveResult(null, field)); } decl.ReturnType = ConvertType(field.ReturnType); + if (decl.ReturnType is ComposedType ct && ct.HasRefSpecifier && field.ReturnTypeIsRefReadOnly) + { + ct.HasReadOnlySpecifier = true; + } Expression initializer = null; if (field.IsConst && this.ShowConstantValues) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs index b77494f46..e357dfa44 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs @@ -494,6 +494,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms // C# doesn't allow mutation of value-type temporaries return true; default: + if (addr.MatchLdFld(out _, out var field)) + return field.ReturnTypeIsRefReadOnly; return false; } } diff --git a/ICSharpCode.Decompiler/TypeSystem/IField.cs b/ICSharpCode.Decompiler/TypeSystem/IField.cs index 49a92fd26..42e13c40e 100644 --- a/ICSharpCode.Decompiler/TypeSystem/IField.cs +++ b/ICSharpCode.Decompiler/TypeSystem/IField.cs @@ -35,6 +35,11 @@ namespace ICSharpCode.Decompiler.TypeSystem /// bool IsReadOnly { get; } + /// + /// Gets whether the field type is 'ref readonly'. + /// + bool ReturnTypeIsRefReadOnly { get; } + /// /// Gets whether this field is volatile. /// diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs index d6b786201..405fff29b 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs @@ -238,6 +238,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation case SymbolKind.ReturnType: case SymbolKind.Property: case SymbolKind.Indexer: + case SymbolKind.Field: return true; // "ref readonly" is currently always active default: return false; diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs index 2c4a2f67e..ca9b556d7 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs @@ -109,6 +109,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation } bool IField.IsReadOnly => false; + bool IField.ReturnTypeIsRefReadOnly => false; bool IField.IsVolatile => false; bool IVariable.IsConst => false; diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataField.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataField.cs index c8e045753..d561e3be2 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataField.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataField.cs @@ -189,6 +189,13 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation return b.GetAttribute(metadata, def.GetCustomAttributes(), attribute, SymbolKind.Field); } + public bool ReturnTypeIsRefReadOnly { + get { + var def = module.metadata.GetFieldDefinition(handle); + return def.GetCustomAttributes().HasKnownAttribute(module.metadata, KnownAttribute.IsReadOnly); + } + } + public string FullName => $"{DeclaringType?.FullName}.{Name}"; public string ReflectionName => $"{DeclaringType?.ReflectionName}.{Name}"; public string Namespace => DeclaringType?.Namespace ?? string.Empty; diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedField.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedField.cs index ac0dc9c40..fe4dc9ccc 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedField.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedField.cs @@ -45,13 +45,9 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation AddSubstitution(substitution); } - public bool IsReadOnly { - get { return fieldDefinition.IsReadOnly; } - } - - public bool IsVolatile { - get { return fieldDefinition.IsVolatile; } - } + public bool IsReadOnly => fieldDefinition.IsReadOnly; + public bool ReturnTypeIsRefReadOnly => fieldDefinition.ReturnTypeIsRefReadOnly; + public bool IsVolatile => fieldDefinition.IsVolatile; IType IVariable.Type { get { return this.ReturnType; }