diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 19bb00805..15aa15071 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -911,16 +911,10 @@ namespace ICSharpCode.Decompiler.CSharp // Remove the [DefaultMember] attribute if the class contains indexers RemoveAttribute(typeDecl, KnownAttribute.DefaultMember); } - if (settings.IntroduceRefAndReadonlyModifiersOnStructs && typeDecl.ClassType == ClassType.Struct) { - if (RemoveAttribute(typeDecl, KnownAttribute.IsByRefLike)) { - typeDecl.Modifiers |= Modifiers.Ref; - } - if (RemoveAttribute(typeDecl, KnownAttribute.IsReadOnly)) { - typeDecl.Modifiers |= Modifiers.Readonly; - } + if (settings.IntroduceRefModifiersOnStructs) { if (FindAttribute(typeDecl, KnownAttribute.Obsolete, out var attr)) { if (obsoleteAttributePattern.IsMatch(attr)) { - if (attr.Parent is Syntax.AttributeSection section && section.Attributes.Count == 1) + if (attr.Parent is AttributeSection section && section.Attributes.Count == 1) section.Remove(); else attr.Remove(); diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index 52445918c..158911cb5 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -2170,6 +2170,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor case ParameterModifier.This: WriteKeyword(ParameterDeclaration.ThisModifierRole); break; + case ParameterModifier.In: + WriteKeyword(ParameterDeclaration.InModifierRole); + break; } parameterDeclaration.Type.AcceptVisitor(this); if (!parameterDeclaration.Type.IsNull && !string.IsNullOrEmpty(parameterDeclaration.Name)) { diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/ParameterDeclaration.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/ParameterDeclaration.cs index fad67c387..a3be5547a 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/ParameterDeclaration.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/ParameterDeclaration.cs @@ -32,7 +32,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax Ref, Out, Params, - This + This, + In } public class ParameterDeclaration : AstNode @@ -42,7 +43,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax public static readonly TokenRole OutModifierRole = new TokenRole("out"); public static readonly TokenRole ParamsModifierRole = new TokenRole("params"); public static readonly TokenRole ThisModifierRole = new TokenRole("this"); - + public static readonly TokenRole InModifierRole = new TokenRole("in"); + public override NodeType NodeType { get { return NodeType.Unknown; diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index 54a6021dd..e2e627d05 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -918,6 +918,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax decl.ParameterModifier = ParameterModifier.Ref; } else if (parameter.IsOut) { decl.ParameterModifier = ParameterModifier.Out; + } else if (parameter.IsIn) { + decl.ParameterModifier = ParameterModifier.In; } else if (parameter.IsParams) { decl.ParameterModifier = ParameterModifier.Params; } @@ -1014,6 +1016,12 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax case TypeKind.Struct: classType = ClassType.Struct; modifiers &= ~Modifiers.Sealed; + if (typeDefinition.IsReadOnly) { + modifiers |= Modifiers.Readonly; + } + if (typeDefinition.IsByRefLike) { + modifiers |= Modifiers.Ref; + } break; case TypeKind.Enum: classType = ClassType.Enum; diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index ef08fa1ac..39c24387f 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -83,7 +83,8 @@ namespace ICSharpCode.Decompiler discards = false; } if (languageVersion < CSharp.LanguageVersion.CSharp7_2) { - introduceRefAndReadonlyModifiersOnStructs = false; + introduceReadonlyAndInModifiers = false; + introduceRefModifiersOnStructs = false; nonTrailingNamedArguments = false; } if (languageVersion < CSharp.LanguageVersion.CSharp7_3) { @@ -96,7 +97,7 @@ namespace ICSharpCode.Decompiler { if (tupleComparisons) return CSharp.LanguageVersion.CSharp7_3; - if (IntroduceRefAndReadonlyModifiersOnStructs || nonTrailingNamedArguments) + if (introduceRefModifiersOnStructs || introduceReadonlyAndInModifiers || nonTrailingNamedArguments) return CSharp.LanguageVersion.CSharp7_2; // C# 7.1 missing if (outVariables || tupleTypes || tupleConversions || discards) @@ -650,16 +651,32 @@ namespace ICSharpCode.Decompiler } } - bool introduceRefAndReadonlyModifiersOnStructs = true; + bool introduceRefModifiersOnStructs = true; /// - /// Gets/Sets whether IsByRefLikeAttribute and IsReadOnlyAttribute should be replaced with 'ref' and 'readonly' modifiers on structs. + /// Gets/Sets whether IsByRefLikeAttribute should be replaced with 'ref' modifiers on structs. /// - public bool IntroduceRefAndReadonlyModifiersOnStructs { - get { return introduceRefAndReadonlyModifiersOnStructs; } + public bool IntroduceRefModifiersOnStructs { + get { return introduceRefModifiersOnStructs; } set { - if (introduceRefAndReadonlyModifiersOnStructs != value) { - introduceRefAndReadonlyModifiersOnStructs = value; + if (introduceRefModifiersOnStructs != value) { + introduceRefModifiersOnStructs = value; + OnPropertyChanged(); + } + } + } + + bool introduceReadonlyAndInModifiers = true; + + /// + /// Gets/Sets whether IsReadOnlyAttribute should be replaced with 'readonly' modifiers on structs + /// and with the 'in' modifier on parameters. + /// + public bool IntroduceReadonlyAndInModifiers { + get { return introduceReadonlyAndInModifiers; } + set { + if (introduceReadonlyAndInModifiers != value) { + introduceReadonlyAndInModifiers = value; OnPropertyChanged(); } } diff --git a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs index 602290f10..71bc5a4e7 100644 --- a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs +++ b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs @@ -84,9 +84,19 @@ namespace ICSharpCode.Decompiler.TypeSystem /// KeepModifiers = 0x40, /// + /// If this option is active, [IsReadOnlyAttribute] 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, + /// + /// If this option is active, [IsByRefLikeAttribute] is removed and structs are marked as ref. + /// Otherwise, the attribute is preserved but the structs are not marked. + /// + RefStructs = 0x100, + /// /// Default settings: typical options for the decompiler, with all C# languages features enabled. /// - Default = Dynamic | Tuple | ExtensionMethods | DecimalConstants + Default = Dynamic | Tuple | ExtensionMethods | DecimalConstants | ReadOnlyStructsAndParameters | RefStructs } /// @@ -108,6 +118,10 @@ namespace ICSharpCode.Decompiler.TypeSystem typeSystemOptions |= TypeSystemOptions.ExtensionMethods; if (settings.DecimalConstants) typeSystemOptions |= TypeSystemOptions.DecimalConstants; + if (settings.IntroduceRefModifiersOnStructs) + typeSystemOptions |= TypeSystemOptions.RefStructs; + if (settings.IntroduceReadonlyAndInModifiers) + typeSystemOptions |= TypeSystemOptions.ReadOnlyStructsAndParameters; return typeSystemOptions; } diff --git a/ICSharpCode.Decompiler/TypeSystem/IParameter.cs b/ICSharpCode.Decompiler/TypeSystem/IParameter.cs index 553d8f029..68f10c532 100644 --- a/ICSharpCode.Decompiler/TypeSystem/IParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/IParameter.cs @@ -37,7 +37,12 @@ namespace ICSharpCode.Decompiler.TypeSystem /// Gets whether this parameter is a C# 'out' parameter. /// bool IsOut { get; } - + + /// + /// Gets whether this parameter is a C# 'in' parameter. + /// + bool IsIn { get; } + /// /// Gets whether this parameter is a C# 'params' parameter. /// diff --git a/ICSharpCode.Decompiler/TypeSystem/ITypeDefinition.cs b/ICSharpCode.Decompiler/TypeSystem/ITypeDefinition.cs index dbb2e1162..2a3d750ed 100644 --- a/ICSharpCode.Decompiler/TypeSystem/ITypeDefinition.cs +++ b/ICSharpCode.Decompiler/TypeSystem/ITypeDefinition.cs @@ -44,6 +44,12 @@ namespace ICSharpCode.Decompiler.TypeSystem /// For all other types: returns . /// IType EnumUnderlyingType { get; } + + /// + /// For structs: returns whether this is a readonly struct. + /// For all other types: returns false. + /// + bool IsReadOnly { get; } /// /// Gets the full name of this type. diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs index 5e53daafb..867521eb7 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs @@ -200,6 +200,10 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation return (options & TypeSystemOptions.ExtensionMethods) != 0; case "DecimalConstantAttribute": return (options & TypeSystemOptions.DecimalConstants) != 0; + case "IsReadOnlyAttribute": + return (options & TypeSystemOptions.ReadOnlyStructsAndParameters) != 0; + case "IsByRefLikeAttribute": + return (options & TypeSystemOptions.RefStructs) != 0; default: return false; } diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultParameter.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultParameter.cs index 7642ad98c..00ebd3088 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultParameter.cs @@ -31,7 +31,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation readonly IType type; readonly string name; readonly IReadOnlyList attributes; - readonly bool isRef, isOut, isParams, isOptional; + readonly bool isRef, isOut, isIn, isParams, isOptional; readonly object defaultValue; readonly IParameterizedMember owner; @@ -47,7 +47,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation } public DefaultParameter(IType type, string name, IParameterizedMember owner = null, IReadOnlyList attributes = null, - bool isRef = false, bool isOut = false, bool isParams = false, bool isOptional = false, object defaultValue = null) + bool isRef = false, bool isOut = false, bool isIn = false, bool isParams = false, bool isOptional = false, object defaultValue = null) { if (type == null) throw new ArgumentNullException("type"); @@ -59,6 +59,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation this.attributes = attributes ?? EmptyList.Instance; this.isRef = isRef; this.isOut = isOut; + this.isIn = isIn; this.isParams = isParams; this.isOptional = isOptional; this.defaultValue = defaultValue; @@ -81,6 +82,10 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation public bool IsOut { get { return isOut; } } + + public bool IsIn { + get { return IsIn; } + } public bool IsParams { get { return isParams; } diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs index 18871d920..eabc198f4 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs @@ -66,7 +66,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation if (IsOptional && !HasConstantValueInSignature) b.Add(KnownAttribute.Optional); - if (!IsOut) { + if (!IsOut && !IsIn) { if ((attributes & ParameterAttributes.In) == ParameterAttributes.In) b.Add(KnownAttribute.In); if ((attributes & ParameterAttributes.Out) == ParameterAttributes.Out) @@ -80,17 +80,28 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation #endregion const ParameterAttributes inOut = ParameterAttributes.In | ParameterAttributes.Out; - public bool IsRef => Type.Kind == TypeKind.ByReference && (attributes & inOut) != ParameterAttributes.Out; + public bool IsRef => Type.Kind == TypeKind.ByReference && (attributes & inOut) == 0; public bool IsOut => Type.Kind == TypeKind.ByReference && (attributes & inOut) == ParameterAttributes.Out; public bool IsOptional => (attributes & ParameterAttributes.Optional) != 0; + public bool IsIn { + get { + if ((module.TypeSystemOptions & TypeSystemOptions.ReadOnlyStructsAndParameters) == 0 || + Type.Kind != TypeKind.ByReference || (attributes & inOut) != ParameterAttributes.In) + return false; + var metadata = module.metadata; + var parameterDef = metadata.GetParameter(handle); + return parameterDef.GetCustomAttributes().HasKnownAttribute(metadata, KnownAttribute.IsReadOnly); + } + } + public bool IsParams { get { if (Type.Kind != TypeKind.Array) return false; var metadata = module.metadata; - var propertyDef = metadata.GetParameter(handle); - return propertyDef.GetCustomAttributes().HasKnownAttribute(metadata, KnownAttribute.ParamArray); + var parameterDef = metadata.GetParameter(handle); + return parameterDef.GetCustomAttributes().HasKnownAttribute(metadata, KnownAttribute.ParamArray); } } @@ -100,8 +111,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation if (name != null) return name; var metadata = module.metadata; - var propertyDef = metadata.GetParameter(handle); - return LazyInit.GetOrSet(ref this.name, metadata.GetString(propertyDef.Name)); + var parameterDef = metadata.GetParameter(handle); + return LazyInit.GetOrSet(ref this.name, metadata.GetString(parameterDef.Name)); } } diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeDefinition.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeDefinition.cs index b3121892d..d053a6bdb 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeDefinition.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeDefinition.cs @@ -45,6 +45,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation readonly TypeAttributes attributes; public TypeKind Kind { get; } public bool IsByRefLike { get; } + public bool IsReadOnly { get; } public ITypeDefinition DeclaringTypeDefinition { get; } public IReadOnlyList TypeParameters { get; } public KnownTypeCode KnownTypeCode { get; } @@ -101,12 +102,14 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation } else { this.Kind = TypeKind.Struct; this.IsByRefLike = td.GetCustomAttributes().HasKnownAttribute(metadata, KnownAttribute.IsByRefLike); + this.IsReadOnly = td.GetCustomAttributes().HasKnownAttribute(metadata, KnownAttribute.IsReadOnly); } } else if (td.IsDelegate(metadata)) { this.Kind = TypeKind.Delegate; } else { this.Kind = TypeKind.Class; this.HasExtensionMethods = this.IsStatic + && (module.TypeSystemOptions & TypeSystemOptions.ExtensionMethods) == TypeSystemOptions.ExtensionMethods && td.GetCustomAttributes().HasKnownAttribute(metadata, KnownAttribute.Extension); } } diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MinimalCorlib.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MinimalCorlib.cs index 704daf99a..2cb4332a6 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MinimalCorlib.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MinimalCorlib.cs @@ -156,6 +156,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation IType IEntity.DeclaringType => null; bool ITypeDefinition.HasExtensionMethods => false; + bool ITypeDefinition.IsReadOnly => false; TypeKind IType.Kind => typeKind; diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedParameter.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedParameter.cs index bd34dbe92..40459fb48 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedParameter.cs @@ -38,6 +38,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation IEnumerable IParameter.GetAttributes() => baseParameter.GetAttributes(); bool IParameter.IsRef => baseParameter.IsRef; bool IParameter.IsOut => baseParameter.IsOut; + bool IParameter.IsIn => baseParameter.IsIn; bool IParameter.IsParams => baseParameter.IsParams; bool IParameter.IsOptional => baseParameter.IsOptional; bool IParameter.HasConstantValueInSignature => baseParameter.HasConstantValueInSignature;