diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs
index 250b2f1ef..f17a64a52 100644
--- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs
+++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs
@@ -224,6 +224,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
preprocessorSymbols.Add("CS71");
preprocessorSymbols.Add("CS72");
preprocessorSymbols.Add("CS73");
+ preprocessorSymbols.Add("CS80");
preprocessorSymbols.Add("VB11");
preprocessorSymbols.Add("VB14");
preprocessorSymbols.Add("VB15");
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs
index 665be1a32..a78bf77d3 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs
@@ -63,9 +63,57 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
private readonly int dummy;
+ public int Property {
+ get {
+ return 1;
+ }
+ set {
+ }
+ }
+
+#if CS80
+ public readonly int ReadOnlyProperty {
+ get {
+ return 1;
+ }
+ set {
+ }
+ }
+
+ public int PropertyWithReadOnlyGetter {
+ readonly get {
+ return 1;
+ }
+ set {
+ }
+ }
+
+ public int PropertyWithReadOnlySetter {
+ get {
+ return 1;
+ }
+ readonly set {
+ }
+ }
+
+ public event EventHandler NormalEvent;
+
+ public readonly event EventHandler ReadOnlyEvent {
+ add {
+ }
+ remove {
+ }
+ }
+#endif
public void Method()
{
}
+
+#if CS80
+ public readonly void ReadOnlyMethod()
+ {
+ }
+#endif
}
public readonly struct ReadOnlyStruct
diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
index 589f1042c..f39442bd8 100644
--- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
@@ -1522,6 +1522,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
Accessor decl = new Accessor();
if (this.ShowAccessibility && accessor.Accessibility != ownerAccessibility)
decl.Modifiers = ModifierFromAccessibility(accessor.Accessibility);
+ if (accessor.ThisIsRefReadOnly && accessor.DeclaringTypeDefinition?.IsReadOnly == false)
+ decl.Modifiers |= Modifiers.Readonly;
if (ShowAttributes) {
decl.Attributes.AddRange(ConvertAttributes(accessor.GetAttributes()));
decl.Attributes.AddRange(ConvertAttributes(accessor.GetReturnTypeAttributes(), "return"));
@@ -1551,9 +1553,18 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
decl.Getter = ConvertAccessor(property.Getter, property.Accessibility, false);
decl.Setter = ConvertAccessor(property.Setter, property.Accessibility, true);
decl.PrivateImplementationType = GetExplicitInterfaceType (property);
+ MergeReadOnlyModifiers(decl, decl.Getter, decl.Setter);
return decl;
}
+ static void MergeReadOnlyModifiers(EntityDeclaration decl, Accessor accessor1, Accessor accessor2)
+ {
+ if (accessor1.HasModifier(Modifiers.Readonly) && accessor2.HasModifier(Modifiers.Readonly)) {
+ accessor1.Modifiers &= ~Modifiers.Readonly;
+ accessor2.Modifiers &= ~Modifiers.Readonly;
+ decl.Modifiers |= Modifiers.Readonly;
+ }
+ }
IndexerDeclaration ConvertIndexer(IProperty indexer)
{
IndexerDeclaration decl = new IndexerDeclaration();
@@ -1571,6 +1582,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
decl.Getter = ConvertAccessor(indexer.Getter, indexer.Accessibility, false);
decl.Setter = ConvertAccessor(indexer.Setter, indexer.Accessibility, true);
decl.PrivateImplementationType = GetExplicitInterfaceType (indexer);
+ MergeReadOnlyModifiers(decl, decl.Getter, decl.Setter);
return decl;
}
@@ -1590,6 +1602,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
decl.AddAccessor = ConvertAccessor(ev.AddAccessor, ev.Accessibility, true);
decl.RemoveAccessor = ConvertAccessor(ev.RemoveAccessor, ev.Accessibility, true);
decl.PrivateImplementationType = GetExplicitInterfaceType (ev);
+ MergeReadOnlyModifiers(decl, decl.AddAccessor, decl.RemoveAccessor);
return decl;
} else {
EventDeclaration decl = new EventDeclaration();
@@ -1759,6 +1772,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
m |= Modifiers.Virtual;
if (member.IsSealed)
m |= Modifiers.Sealed;
+ if (member is IMethod method && method.ThisIsRefReadOnly && method.DeclaringTypeDefinition?.IsReadOnly == false)
+ m |= Modifiers.Readonly;
}
}
return m;
diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs
index faa6a3163..8692bf678 100644
--- a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs
+++ b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs
@@ -571,11 +571,14 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
}
if (field == null)
return null;
+ if (propertyDeclaration.Setter.HasModifier(Modifiers.Readonly))
+ return null;
if (field.IsCompilerGenerated() && field.DeclaringTypeDefinition == property.DeclaringTypeDefinition) {
RemoveCompilerGeneratedAttribute(propertyDeclaration.Getter.Attributes);
RemoveCompilerGeneratedAttribute(propertyDeclaration.Setter.Attributes);
propertyDeclaration.Getter.Body = null;
propertyDeclaration.Setter.Body = null;
+ propertyDeclaration.Getter.Modifiers &= ~Modifiers.Readonly;
// Add C# 7.3 attributes on backing field:
var attributes = field.GetAttributes()
diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs
index 167961528..40aa62e4a 100644
--- a/ICSharpCode.Decompiler/DecompilerSettings.cs
+++ b/ICSharpCode.Decompiler/DecompilerSettings.cs
@@ -107,12 +107,13 @@ namespace ICSharpCode.Decompiler
}
if (languageVersion < CSharp.LanguageVersion.CSharp8_0) {
nullableReferenceTypes = false;
+ readOnlyMethods = false;
}
}
public CSharp.LanguageVersion GetMinimumRequiredVersion()
{
- if (nullableReferenceTypes)
+ if (nullableReferenceTypes || readOnlyMethods)
return CSharp.LanguageVersion.CSharp8_0;
if (introduceUnmanagedConstraint || tupleComparisons || stackAllocInitializers)
return CSharp.LanguageVersion.CSharp7_3;
@@ -842,6 +843,20 @@ namespace ICSharpCode.Decompiler
}
}
+ bool readOnlyMethods = true;
+
+ [Category("C# 8.0 / VS 2019")]
+ [Description("DecompilerSettings.IsReadOnlyAttributeShouldBeReplacedWithReadonlyInModifiersOnStructsParameters")]
+ public bool ReadOnlyMethods {
+ get { return readOnlyMethods; }
+ set {
+ if (readOnlyMethods != value) {
+ readOnlyMethods = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
bool introduceUnmanagedConstraint = true;
///
diff --git a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs
index 04ce3ef8f..4ba6622bf 100644
--- a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs
@@ -84,7 +84,8 @@ namespace ICSharpCode.Decompiler.TypeSystem
///
KeepModifiers = 0x40,
///
- /// If this option is active, [IsReadOnlyAttribute] is removed and parameters are marked as in, structs as readonly.
+ /// If this option is active, [IsReadOnlyAttribute] on parameters+structs is removed
+ /// and parameters are marked as in, structs as readonly.
/// Otherwise, the attribute is preserved but the parameters and structs are not marked.
///
ReadOnlyStructsAndParameters = 0x80,
@@ -104,10 +105,15 @@ namespace ICSharpCode.Decompiler.TypeSystem
///
NullabilityAnnotations = 0x400,
///
+ /// If this option is active, [IsReadOnlyAttribute] on methods is removed
+ /// and the method marked as ThisIsRefReadOnly.
+ ///
+ ReadOnlyMethods = 0x800,
+ ///
/// Default settings: typical options for the decompiler, with all C# languages features enabled.
///
Default = Dynamic | Tuple | ExtensionMethods | DecimalConstants | ReadOnlyStructsAndParameters
- | RefStructs | UnmanagedConstraints | NullabilityAnnotations
+ | RefStructs | UnmanagedConstraints | NullabilityAnnotations | ReadOnlyMethods
}
///
@@ -137,6 +143,8 @@ namespace ICSharpCode.Decompiler.TypeSystem
typeSystemOptions |= TypeSystemOptions.UnmanagedConstraints;
if (settings.NullableReferenceTypes)
typeSystemOptions |= TypeSystemOptions.NullabilityAnnotations;
+ if (settings.ReadOnlyMethods)
+ typeSystemOptions |= TypeSystemOptions.ReadOnlyMethods;
return typeSystemOptions;
}
diff --git a/ICSharpCode.Decompiler/TypeSystem/IMethod.cs b/ICSharpCode.Decompiler/TypeSystem/IMethod.cs
index 1c98eb476..ad1c177d6 100644
--- a/ICSharpCode.Decompiler/TypeSystem/IMethod.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/IMethod.cs
@@ -41,7 +41,8 @@ namespace ICSharpCode.Decompiler.TypeSystem
bool ReturnTypeIsRefReadOnly { get; }
///
- /// Gets whether the method is readonly (C# 8): accepts the 'this' reference as ref readonly
+ /// Gets whether the method accepts the 'this' reference as ref readonly.
+ /// This can be either because the method is C# 8.0 'readonly', or because it is within a C# 7.2 'readonly struct'
///
bool ThisIsRefReadOnly { get; }
diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs
index 1bc076ad9..920106c37 100644
--- a/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs
@@ -201,7 +201,16 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
case "DecimalConstantAttribute":
return (options & TypeSystemOptions.DecimalConstants) != 0 && (target == SymbolKind.Field || target == SymbolKind.Parameter);
case "IsReadOnlyAttribute":
- return (options & TypeSystemOptions.ReadOnlyStructsAndParameters) != 0;
+ switch (target) {
+ case SymbolKind.TypeDefinition:
+ case SymbolKind.Parameter:
+ return (options & TypeSystemOptions.ReadOnlyStructsAndParameters) != 0;
+ case SymbolKind.Method:
+ case SymbolKind.Accessor:
+ return (options & TypeSystemOptions.ReadOnlyMethods) != 0;
+ default:
+ return false;
+ }
case "IsByRefLikeAttribute":
return (options & TypeSystemOptions.RefStructs) != 0 && target == SymbolKind.TypeDefinition;
case "IsUnmanagedAttribute":
diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs
index 57e096823..881933acd 100644
--- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs
@@ -49,6 +49,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
IParameter[] parameters;
IType returnType;
byte returnTypeIsRefReadonly = ThreeState.Unknown;
+ byte thisIsRefReadonly = ThreeState.Unknown;
internal MetadataMethod(MetadataModule module, MethodDefinitionHandle handle)
{
@@ -424,9 +425,24 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
return hasReadOnlyAttr;
}
}
- #endregion
- public bool ThisIsRefReadOnly => DeclaringTypeDefinition?.IsReadOnly ?? false;
+ public bool ThisIsRefReadOnly {
+ get {
+ if (thisIsRefReadonly != ThreeState.Unknown) {
+ return thisIsRefReadonly == ThreeState.True;
+ }
+ var metadata = module.metadata;
+ var methodDefinition = metadata.GetMethodDefinition(handle);
+ bool hasReadOnlyAttr = DeclaringTypeDefinition?.IsReadOnly ?? false;
+ if ((module.TypeSystemOptions & TypeSystemOptions.ReadOnlyMethods) != 0) {
+ hasReadOnlyAttr |= methodDefinition.GetCustomAttributes().HasKnownAttribute(metadata, KnownAttribute.IsReadOnly);
+ }
+ this.thisIsRefReadonly = ThreeState.From(hasReadOnlyAttr);
+ return hasReadOnlyAttr;
+ }
+ }
+
+ #endregion
public Accessibility Accessibility => GetAccessibility(attributes);