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; }