diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs index 934d4a6fe..62486e6e0 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs @@ -129,7 +129,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } -#if ROSLYN4 + +#if CS100 internal class RecordStructs { public record struct Base(string A); @@ -230,6 +231,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } } +#endif +#if CS110 + #endif } namespace System.Runtime.CompilerServices diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Structs.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Structs.cs index fd5dedfda..c796990eb 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Structs.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Structs.cs @@ -51,4 +51,28 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } #endif -} \ No newline at end of file + +#if CS110 + public struct StructWithRequiredMembers + { + public required string FirstName; + public required string LastName { get; set; } + } +#endif +} + +namespace System.Runtime.CompilerServices +{ + [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] + internal sealed class CompilerFeatureRequiredAttribute : Attribute + { + public CompilerFeatureRequiredAttribute(string featureName) + { + } + } + + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)] + internal sealed class RequiredMemberAttribute : Attribute + { + } +} diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 3371ce180..087b509c0 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -1380,6 +1380,10 @@ namespace ICSharpCode.Decompiler.CSharp } } } + if (settings.RequiredMembers) + { + RemoveAttribute(typeDecl, KnownAttribute.RequiredAttribute); + } if (typeDecl.ClassType == ClassType.Enum) { switch (decompileRun.EnumValueDisplayMode) @@ -1871,6 +1875,10 @@ namespace ICSharpCode.Decompiler.CSharp typeSystemAstBuilder.UseSpecialConstants = !(field.DeclaringType.Equals(field.ReturnType) || isMathPIOrE); var fieldDecl = typeSystemAstBuilder.ConvertEntity(field); SetNewModifier(fieldDecl); + if (settings.RequiredMembers && RemoveAttribute(fieldDecl, KnownAttribute.RequiredAttribute)) + { + fieldDecl.Modifiers |= Modifiers.Required; + } if (settings.FixedBuffers && IsFixedField(field, out var elementType, out var elementCount)) { var fixedFieldDecl = new FixedFieldDeclaration(); @@ -1983,6 +1991,10 @@ namespace ICSharpCode.Decompiler.CSharp propertyDecl.Modifiers &= ~(Modifiers.New | Modifiers.Virtual); propertyDecl.Modifiers |= Modifiers.Override; } + if (settings.RequiredMembers && RemoveAttribute(propertyDecl, KnownAttribute.RequiredAttribute)) + { + propertyDecl.Modifiers |= Modifiers.Required; + } return propertyDecl; } catch (Exception innerException) when (!(innerException is OperationCanceledException || innerException is DecompilerException)) diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/CSharpModifierToken.cs b/ICSharpCode.Decompiler/CSharp/Syntax/CSharpModifierToken.cs index b81a7d76b..bb0f98198 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/CSharpModifierToken.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/CSharpModifierToken.cs @@ -67,7 +67,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax Modifiers.New, Modifiers.Unsafe, Modifiers.Abstract, Modifiers.Virtual, Modifiers.Sealed, Modifiers.Static, Modifiers.Override, - Modifiers.Readonly, Modifiers.Volatile, + Modifiers.Required, Modifiers.Readonly, Modifiers.Volatile, Modifiers.Ref, Modifiers.Extern, Modifiers.Partial, Modifiers.Const, Modifiers.Async, @@ -119,6 +119,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax return "async"; case Modifiers.Ref: return "ref"; + case Modifiers.Required: + return "required"; case Modifiers.Any: // even though it's used for pattern matching only, 'any' needs to be in this list to be usable in the AST return "any"; @@ -129,50 +131,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax public static int GetModifierLength(Modifiers modifier) { - switch (modifier) - { - case Modifiers.Private: - return "private".Length; - case Modifiers.Internal: - return "internal".Length; - case Modifiers.Protected: - return "protected".Length; - case Modifiers.Public: - return "public".Length; - case Modifiers.Abstract: - return "abstract".Length; - case Modifiers.Virtual: - return "virtual".Length; - case Modifiers.Sealed: - return "sealed".Length; - case Modifiers.Static: - return "static".Length; - case Modifiers.Override: - return "override".Length; - case Modifiers.Readonly: - return "readonly".Length; - case Modifiers.Const: - return "const".Length; - case Modifiers.New: - return "new".Length; - case Modifiers.Partial: - return "partial".Length; - case Modifiers.Extern: - return "extern".Length; - case Modifiers.Volatile: - return "volatile".Length; - case Modifiers.Unsafe: - return "unsafe".Length; - case Modifiers.Async: - return "async".Length; - case Modifiers.Ref: - return "ref".Length; - case Modifiers.Any: - // even though it's used for pattern matching only, 'any' needs to be in this list to be usable in the AST - return "any".Length; - default: - throw new NotSupportedException("Invalid value for Modifiers"); - } + return GetModifierName(modifier).Length; } public static Modifiers GetModifierValue(string modifier) @@ -215,6 +174,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax return Modifiers.Async; case "ref": return Modifiers.Ref; + case "required": + return Modifiers.Required; case "any": // even though it's used for pattern matching only, 'any' needs to be in this list to be usable in the AST return Modifiers.Any; diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Modifiers.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Modifiers.cs index 98464b09c..43804b1cb 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/Modifiers.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Modifiers.cs @@ -55,6 +55,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax Unsafe = 0x8000, Async = 0x10000, Ref = 0x20000, + Required = 0x40000, VisibilityMask = Private | Internal | Protected | Public, diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index d00f198f0..4ce0c44c0 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -151,12 +151,13 @@ namespace ICSharpCode.Decompiler { parameterNullCheck = false; lifetimeAnnotations = false; + requiredMembers = false; } } public CSharp.LanguageVersion GetMinimumRequiredVersion() { - if (parameterNullCheck || lifetimeAnnotations) + if (parameterNullCheck || lifetimeAnnotations || requiredMembers) return CSharp.LanguageVersion.CSharp11_0; if (fileScopedNamespaces || recordStructs) return CSharp.LanguageVersion.CSharp10_0; @@ -356,6 +357,24 @@ namespace ICSharpCode.Decompiler } } + bool requiredMembers = true; + + /// + /// Use C# 11 required modifier. + /// + [Category("C# 11.0 / VS 2022.4")] + [Description("DecompilerSettings.RequiredMembers")] + public bool RequiredMembers { + get { return requiredMembers; } + set { + if (requiredMembers != value) + { + requiredMembers = value; + OnPropertyChanged(); + } + } + } + bool switchExpressions = true; /// diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs index f0263c370..aa289b498 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs @@ -106,11 +106,14 @@ namespace ICSharpCode.Decompiler.TypeSystem // C# 9 attributes: NativeInteger, PreserveBaseOverrides, + + // C# 11 attributes: + RequiredAttribute, } public static class KnownAttributes { - internal const int Count = (int)KnownAttribute.PreserveBaseOverrides + 1; + internal const int Count = (int)KnownAttribute.RequiredAttribute + 1; static readonly TopLevelTypeName[] typeNames = new TopLevelTypeName[Count]{ default, @@ -175,6 +178,8 @@ namespace ICSharpCode.Decompiler.TypeSystem // C# 9 attributes: new TopLevelTypeName("System.Runtime.CompilerServices", "NativeIntegerAttribute"), new TopLevelTypeName("System.Runtime.CompilerServices", "PreserveBaseOverridesAttribute"), + // C# 11 attributes: + new TopLevelTypeName("System.Runtime.CompilerServices", "RequiredMemberAttribute"), }; public static ref readonly TopLevelTypeName GetTypeName(this KnownAttribute attr) diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs index a90649709..60114829a 100644 --- a/ILSpy/Properties/Resources.Designer.cs +++ b/ILSpy/Properties/Resources.Designer.cs @@ -1226,6 +1226,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// Looks up a localized string similar to Required members. + /// + public static string DecompilerSettings_RequiredMembers { + get { + return ResourceManager.GetString("DecompilerSettings.RequiredMembers", resourceCulture); + } + } + /// /// Looks up a localized string similar to Separate local variable declarations and initializers (int x = 5; -> int x; x = 5;), if possible. /// diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx index a3b47c1e2..d29a1c274 100644 --- a/ILSpy/Properties/Resources.resx +++ b/ILSpy/Properties/Resources.resx @@ -1054,4 +1054,7 @@ Do you want to continue? _Window + + Required members + \ No newline at end of file