From b75ea2780081d6642e6eb8b322199f4f23e7c5e6 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 8 Aug 2026 14:48:11 +0200 Subject: [PATCH 1/8] Generate DecompilerSettings boilerplate from [DecompilerSetting] attributes Every version-gated setting was bookkept in four places that had to stay in sync by hand: the property boilerplate, SetLanguageVersion, GetMinimumRequiredVersion, and the [Category] display string - and that sync had already drifted in a handful of settings. A new source generator in ICSharpCode.Decompiler.Generators now derives all four from a single [DecompilerSetting] attribute on a partial property: backing field, accessors with change notification, the version-derived [Category], and both version methods. [Description] stays handwritten because its resource keys are irregular and are grepped from the resx. This commit is a 1:1 translation: the current inconsistencies are reproduced exactly (AffectsMinimumRequiredVersion = false on ExtensionMethods, UseLambdaSyntax and UseEnhancedUsing; no gate on SwitchOnReadOnlySpanChar), verified against the old build by comparing SetLanguageVersion and GetMinimumRequiredVersion behavior for every setting at every language version, plus a reflection diff of the full per-property attribute surface. Assisted-by: Claude:claude-fable-5:Claude Code --- .../AnalyzerReleases.Unshipped.md | 4 + .../DecompilerSettingsGenerator.cs | 385 ++++ .../DecompilerSyntaxTreeGenerator.cs | 12 +- .../RoslynHelpers.cs | 15 + ICSharpCode.Decompiler/DecompilerSettings.cs | 1964 +++-------------- 5 files changed, 656 insertions(+), 1724 deletions(-) create mode 100644 ICSharpCode.Decompiler.Generators/DecompilerSettingsGenerator.cs diff --git a/ICSharpCode.Decompiler.Generators/AnalyzerReleases.Unshipped.md b/ICSharpCode.Decompiler.Generators/AnalyzerReleases.Unshipped.md index a0353a503..326331a43 100644 --- a/ICSharpCode.Decompiler.Generators/AnalyzerReleases.Unshipped.md +++ b/ICSharpCode.Decompiler.Generators/AnalyzerReleases.Unshipped.md @@ -6,3 +6,7 @@ Rule ID | Category | Severity | Notes --------|----------|----------|------- DSTG001 | DecompilerSyntaxTreeGenerator | Error | Slot kind must map to a single child type +DSTG002 | DecompilerSettingsGenerator | Error | [DecompilerSetting] target must be a partial instance bool property +DSTG003 | DecompilerSettingsGenerator | Error | Version-gated setting must not declare [Category] +DSTG004 | DecompilerSettingsGenerator | Error | Language version has no display category +DSTG005 | DecompilerSettingsGenerator | Error | Setting must be declared in a non-nested partial class diff --git a/ICSharpCode.Decompiler.Generators/DecompilerSettingsGenerator.cs b/ICSharpCode.Decompiler.Generators/DecompilerSettingsGenerator.cs new file mode 100644 index 000000000..99e87e4b5 --- /dev/null +++ b/ICSharpCode.Decompiler.Generators/DecompilerSettingsGenerator.cs @@ -0,0 +1,385 @@ +// Copyright (c) 2026 Siegfried Pammer +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Collections.Immutable; +using System.Text; + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace ICSharpCode.Decompiler.Generators; + +/// +/// Generates the boilerplate behind [DecompilerSetting] partial properties: the backing field, +/// the accessors with change notification, and - from the per-setting language version - the +/// [Category] attribute plus the SetLanguageVersion and GetMinimumRequiredVersion methods, +/// so that a setting's version is declared in exactly one place. +/// +[Generator] +internal class DecompilerSettingsGenerator : IIncrementalGenerator +{ + static readonly DiagnosticDescriptor InvalidSettingProperty = new( + id: "DSTG002", + title: "[DecompilerSetting] target must be a partial instance bool property", + messageFormat: "Setting property '{0}' must be a partial instance bool property with get and set accessors and an uppercase-start name (the generated backing field uses the camelCase form)", + category: "DecompilerSettingsGenerator", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + // The generated [Category] would join a handwritten one on the merged partial property, and + // GetCustomAttribute() (used by the settings UI) throws on duplicates. + static readonly DiagnosticDescriptor CategoryOnVersionedSetting = new( + id: "DSTG003", + title: "Version-gated setting must not declare [Category]", + messageFormat: "Setting '{0}' derives its [Category] from the language version; remove the handwritten [Category] attribute", + category: "DecompilerSettingsGenerator", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + static readonly DiagnosticDescriptor UnsupportedLanguageVersion = new( + id: "DSTG004", + title: "Language version has no display category", + messageFormat: "Language version '{0}' has no display category; gate settings on a released C# version, or add the new version to DecompilerSettingsGenerator.CategoryByVersion", + category: "DecompilerSettingsGenerator", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + // The generated implementation is emitted as a top-level partial class; a nested or + // non-partial containing type would make it merge nowhere (or into a stray new type). + static readonly DiagnosticDescriptor InvalidContainingType = new( + id: "DSTG005", + title: "Setting must be declared in a non-nested partial class", + messageFormat: "Setting '{0}' must be declared in a partial, non-nested class so the generated implementation merges into it", + category: "DecompilerSettingsGenerator", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + static readonly Dictionary DescriptorsById = + new DiagnosticDescriptor[] { InvalidSettingProperty, CategoryOnVersionedSetting, UnsupportedLanguageVersion, InvalidContainingType } + .ToDictionary(d => d.Id); + + // Display category per released C# version; the settings UI groups options by these strings. + static readonly Dictionary CategoryByVersion = new() { + ["CSharp1"] = "C# 1.0 / VS .NET", + ["CSharp2"] = "C# 2.0 / VS 2005", + ["CSharp3"] = "C# 3.0 / VS 2008", + ["CSharp4"] = "C# 4.0 / VS 2010", + ["CSharp5"] = "C# 5.0 / VS 2012", + ["CSharp6"] = "C# 6.0 / VS 2015", + ["CSharp7"] = "C# 7.0 / VS 2017", + ["CSharp7_1"] = "C# 7.1 / VS 2017.3", + ["CSharp7_2"] = "C# 7.2 / VS 2017.4", + ["CSharp7_3"] = "C# 7.3 / VS 2017.7", + ["CSharp8_0"] = "C# 8.0 / VS 2019", + ["CSharp9_0"] = "C# 9.0 / VS 2019.8", + ["CSharp10_0"] = "C# 10.0 / VS 2022", + ["CSharp11_0"] = "C# 11.0 / VS 2022.4", + ["CSharp12_0"] = "C# 12.0 / VS 2022.8", + ["CSharp13_0"] = "C# 13.0 / VS 2022.12", + ["CSharp14_0"] = "C# 14.0 / VS 2026", + }; + + readonly record struct SettingInfo( + string Namespace, string ClassName, string Accessibility, string PropertyName, string FieldName, + bool DefaultValue, int VersionValue, string? VersionName, string? Category, bool AffectsMinimumRequiredVersion, + string FilePath, int SpanStart); + + // A diagnostic captured during the transform; kept as plain values so the pipeline stays cacheable. + readonly record struct DiagInfo(string Id, string MessageArg, string FilePath, int SpanStart, int SpanLength, + int StartLine, int StartChar, int EndLine, int EndChar); + + readonly record struct SettingResult(SettingInfo? Setting, EquatableArray? Diagnostics); + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + context.RegisterPostInitializationOutput(i => i.AddSource("DecompilerSettingsGeneratorAttributes.g.cs", RoslynHelpers.EmbeddedAttributeSource + @" +namespace ICSharpCode.Decompiler +{ + [global::Microsoft.CodeAnalysis.EmbeddedAttribute] + [global::System.AttributeUsage(global::System.AttributeTargets.Property)] + sealed class DecompilerSettingAttribute : global::System.Attribute + { + public DecompilerSettingAttribute() { } + + public DecompilerSettingAttribute(global::ICSharpCode.Decompiler.CSharp.LanguageVersion introducedIn) { } + + /// Initial value of the setting. Defaults to true. + public bool DefaultValue { get; set; } = true; + + /// + /// Whether enabling the setting raises GetMinimumRequiredVersion() to the version the + /// setting was introduced in. Defaults to true; only meaningful on version-gated settings. + /// + public bool AffectsMinimumRequiredVersion { get; set; } = true; + } +} + +")); + + var settings = context.SyntaxProvider.ForAttributeWithMetadataName( + "ICSharpCode.Decompiler.DecompilerSettingAttribute", + (n, ct) => n is PropertyDeclarationSyntax, + GetSetting); + + context.RegisterSourceOutput(settings.Collect(), WriteSettingsClasses); + } + + static SettingResult GetSetting(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken) + { + var property = (IPropertySymbol)context.TargetSymbol; + var node = (PropertyDeclarationSyntax)context.TargetNode; + var diagnostics = new List(); + + if (property.Type.SpecialType != SpecialType.System_Boolean || property.IsStatic + || property.GetMethod == null || property.SetMethod == null || property.SetMethod.IsInitOnly + || !char.IsUpper(property.Name[0]) + || !node.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword))) + { + diagnostics.Add(MakeDiagInfo(InvalidSettingProperty.Id, property.Name, node)); + return new SettingResult(null, diagnostics.ToEquatableArray()); + } + + if (property.ContainingType.ContainingType != null + || node.Parent is not ClassDeclarationSyntax containingClass + || !containingClass.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword))) + { + diagnostics.Add(MakeDiagInfo(InvalidContainingType.Id, property.Name, node)); + return new SettingResult(null, diagnostics.ToEquatableArray()); + } + + var attribute = context.Attributes[0]; + int versionValue = 0; + string? versionName = null; + string? category = null; + if (attribute.ConstructorArguments.Length == 1) + { + var versionArgument = attribute.ConstructorArguments[0]; + if (versionArgument.Kind == TypedConstantKind.Error || versionArgument.Value is not int boundVersion || versionArgument.Type is null) + { + // The argument did not bind (e.g. a typo'd enum member); the compiler already + // reports that error at the argument, so just skip the setting instead of + // crashing the whole generator. + return new SettingResult(null, null); + } + versionValue = boundVersion; + versionName = VersionNameFromSyntax(attribute) + ?? versionArgument.Type.GetMembers() + .OfType() + .FirstOrDefault(f => f.HasConstantValue && Equals(f.ConstantValue, versionValue))?.Name + ?? versionValue.ToString(); + if (!CategoryByVersion.TryGetValue(versionName, out category)) + { + diagnostics.Add(MakeDiagInfo(UnsupportedLanguageVersion.Id, versionName, node)); + return new SettingResult(null, diagnostics.ToEquatableArray()); + } + if (property.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == "System.ComponentModel.CategoryAttribute")) + { + diagnostics.Add(MakeDiagInfo(CategoryOnVersionedSetting.Id, property.Name, node)); + // The compiler would otherwise also flag the generated [Category] as a duplicate; + // suppress it so the mistake surfaces as the single DSTG003. + category = null; + } + } + + bool defaultValue = true; + bool affectsMinimumRequiredVersion = true; + foreach (var named in attribute.NamedArguments) + { + // A named argument that failed to bind is already a compiler error; ignore it here. + if (named.Value.Value is not bool namedValue) + continue; + if (named.Key == "DefaultValue") + defaultValue = namedValue; + else if (named.Key == "AffectsMinimumRequiredVersion") + affectsMinimumRequiredVersion = namedValue; + } + + string fieldName = char.ToLowerInvariant(property.Name[0]) + property.Name.Substring(1); + if (SyntaxFacts.GetKeywordKind(fieldName) != SyntaxKind.None) + fieldName = "@" + fieldName; + + var setting = new SettingInfo( + property.ContainingNamespace.IsGlobalNamespace ? "" : property.ContainingNamespace.ToDisplayString(), + property.ContainingType.Name, + SyntaxFacts.GetText(property.DeclaredAccessibility), + property.Name, + fieldName, + defaultValue, + versionValue, + versionName, + category, + affectsMinimumRequiredVersion, + node.SyntaxTree.FilePath, + node.SpanStart); + return new SettingResult(setting, diagnostics.Count == 0 ? null : diagnostics.ToEquatableArray()); + } + + // Prefer the enum member name as spelled at the use site: constant values are not unique in + // LanguageVersion (CSharp15_0 and Preview share a value), so a value-based reverse lookup can + // name an alias the user never wrote. + static string? VersionNameFromSyntax(AttributeData attribute) + { + if (attribute.ApplicationSyntaxReference?.GetSyntax() is not AttributeSyntax { ArgumentList.Arguments: { Count: >= 1 } arguments }) + return null; + if (arguments[0].NameEquals != null) + return null; + return arguments[0].Expression switch { + MemberAccessExpressionSyntax memberAccess => memberAccess.Name.Identifier.Text, + IdentifierNameSyntax identifier => identifier.Identifier.Text, + _ => null, + }; + } + + static DiagInfo MakeDiagInfo(string id, string messageArg, SyntaxNode node) + { + var lineSpan = node.GetLocation().GetLineSpan(); + return new DiagInfo(id, messageArg, node.SyntaxTree.FilePath, node.Span.Start, node.Span.Length, + lineSpan.StartLinePosition.Line, lineSpan.StartLinePosition.Character, + lineSpan.EndLinePosition.Line, lineSpan.EndLinePosition.Character); + } + + static void WriteSettingsClasses(SourceProductionContext context, ImmutableArray results) + { + foreach (var result in results) + { + if (result.Diagnostics is not { } resultDiagnostics) + continue; + foreach (var diag in resultDiagnostics) + { + // Indexer lookup so a diagnostic id missing from the map fails loudly instead of + // being reported under an unrelated descriptor. + var descriptor = DescriptorsById[diag.Id]; + var location = Location.Create(diag.FilePath, new TextSpan(diag.SpanStart, diag.SpanLength), + new LinePositionSpan(new LinePosition(diag.StartLine, diag.StartChar), new LinePosition(diag.EndLine, diag.EndChar))); + context.ReportDiagnostic(Diagnostic.Create(descriptor, location, diag.MessageArg)); + } + } + + var settings = results + .Where(r => r.Setting != null) + .Select(r => r.Setting!.Value) + .OrderBy(s => s.FilePath, StringComparer.Ordinal) + .ThenBy(s => s.SpanStart); + + foreach (var settingsClass in settings.GroupBy(s => (s.Namespace, s.ClassName))) + { + WriteSettingsClass(context, settingsClass.Key.Namespace, settingsClass.Key.ClassName, settingsClass.ToArray()); + } + } + + static void WriteSettingsClass(SourceProductionContext context, string ns, string className, SettingInfo[] settings) + { + var builder = new StringBuilder(); + builder.AppendLine("// "); + builder.AppendLine("#nullable enable"); + builder.AppendLine(); + if (ns.Length > 0) + { + builder.AppendLine($"namespace {ns}"); + builder.AppendLine("{"); + } + builder.AppendLine($"\tpartial class {className}"); + builder.AppendLine("\t{"); + + foreach (var setting in settings) + { + builder.AppendLine($"\t\tbool {setting.FieldName} = {(setting.DefaultValue ? "true" : "false")};"); + builder.AppendLine(); + if (setting.Category != null) + { + builder.AppendLine($"\t\t[global::System.ComponentModel.Category(\"{setting.Category}\")]"); + } + builder.AppendLine($"\t\t{setting.Accessibility} partial bool {setting.PropertyName} {{"); + builder.AppendLine($"\t\t\tget {{ return {setting.FieldName}; }}"); + builder.AppendLine("\t\t\tset {"); + builder.AppendLine($"\t\t\t\tif ({setting.FieldName} != value)"); + builder.AppendLine("\t\t\t\t{"); + builder.AppendLine($"\t\t\t\t\t{setting.FieldName} = value;"); + builder.AppendLine("\t\t\t\t\tOnPropertyChanged();"); + builder.AppendLine("\t\t\t\t}"); + builder.AppendLine("\t\t\t}"); + builder.AppendLine("\t\t}"); + builder.AppendLine(); + } + + var versionBuckets = settings + .Where(s => s.VersionName != null) + .GroupBy(s => s.VersionValue) + .OrderBy(g => g.Key) + .ToArray(); + if (versionBuckets.Length > 0) + { + WriteSetLanguageVersion(builder, versionBuckets); + builder.AppendLine(); + WriteGetMinimumRequiredVersion(builder, versionBuckets); + } + + builder.AppendLine("\t}"); + if (ns.Length > 0) + { + builder.AppendLine("}"); + } + // The hint name must carry the full grouping key: two same-named settings classes in + // different namespaces would otherwise collide in AddSource and kill the generator. + string hintName = ns.Length == 0 ? $"{className}.Settings.g.cs" : $"{ns}.{className}.Settings.g.cs"; + context.AddSource(hintName, SourceText.From(builder.ToString().Replace("\r\n", "\n"), Encoding.UTF8)); + } + + static void WriteSetLanguageVersion(StringBuilder builder, IGrouping[] versionBuckets) + { + builder.AppendLine("\t\t/// "); + builder.AppendLine("\t\t/// Deactivates all language features from versions newer than ."); + builder.AppendLine("\t\t/// "); + builder.AppendLine("\t\tpublic void SetLanguageVersion(global::ICSharpCode.Decompiler.CSharp.LanguageVersion languageVersion)"); + builder.AppendLine("\t\t{"); + builder.AppendLine("\t\t\t// By default, all decompiler features are enabled."); + builder.AppendLine("\t\t\t// Disable some of them based on language version:"); + foreach (var bucket in versionBuckets) + { + builder.AppendLine($"\t\t\tif (languageVersion < global::ICSharpCode.Decompiler.CSharp.LanguageVersion.{bucket.First().VersionName})"); + builder.AppendLine("\t\t\t{"); + foreach (var setting in bucket) + { + builder.AppendLine($"\t\t\t\t{setting.FieldName} = false;"); + } + builder.AppendLine("\t\t\t}"); + } + builder.AppendLine("\t\t}"); + } + + static void WriteGetMinimumRequiredVersion(StringBuilder builder, IGrouping[] versionBuckets) + { + builder.AppendLine("\t\t/// "); + builder.AppendLine("\t\t/// Gets the lowest language version that includes all currently enabled language features."); + builder.AppendLine("\t\t/// "); + builder.AppendLine("\t\tpublic global::ICSharpCode.Decompiler.CSharp.LanguageVersion GetMinimumRequiredVersion()"); + builder.AppendLine("\t\t{"); + foreach (var bucket in versionBuckets.Reverse()) + { + var fields = bucket.Where(s => s.AffectsMinimumRequiredVersion).Select(s => s.FieldName).ToArray(); + if (fields.Length == 0) + continue; + builder.AppendLine($"\t\t\tif ({string.Join(" || ", fields)})"); + builder.AppendLine($"\t\t\t\treturn global::ICSharpCode.Decompiler.CSharp.LanguageVersion.{bucket.First().VersionName};"); + } + builder.AppendLine("\t\t\treturn global::ICSharpCode.Decompiler.CSharp.LanguageVersion.CSharp1;"); + builder.AppendLine("\t\t}"); + } +} diff --git a/ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs b/ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs index c5ea313e1..44044d019 100644 --- a/ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs +++ b/ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs @@ -843,17 +843,7 @@ internal class DecompilerSyntaxTreeGenerator : IIncrementalGenerator var visitorMembers = astNodeAdditions.Collect(); context - .RegisterPostInitializationOutput(i => i.AddSource("DecompilerSyntaxTreeGeneratorAttributes.g.cs", @" - -using System; - -namespace Microsoft.CodeAnalysis -{ - internal sealed partial class EmbeddedAttribute : global::System.Attribute - { - } -} - + .RegisterPostInitializationOutput(i => i.AddSource("DecompilerSyntaxTreeGeneratorAttributes.g.cs", RoslynHelpers.EmbeddedAttributeSource + @" namespace ICSharpCode.Decompiler.CSharp.Syntax { [global::Microsoft.CodeAnalysis.EmbeddedAttribute] diff --git a/ICSharpCode.Decompiler.Generators/RoslynHelpers.cs b/ICSharpCode.Decompiler.Generators/RoslynHelpers.cs index c0350704e..60e2ce2ef 100644 --- a/ICSharpCode.Decompiler.Generators/RoslynHelpers.cs +++ b/ICSharpCode.Decompiler.Generators/RoslynHelpers.cs @@ -22,6 +22,21 @@ namespace ICSharpCode.Decompiler.Generators; public static class RoslynHelpers { + /// + /// Post-init source declaring Microsoft.CodeAnalysis.EmbeddedAttribute. Every generator that + /// emits embedded attribute types has to ship its own copy in its own post-init output; the + /// declarations merge only while they stay partial and textually compatible, so all generators + /// must build their copy from this single constant. + /// + public const string EmbeddedAttributeSource = @" +namespace Microsoft.CodeAnalysis +{ + internal sealed partial class EmbeddedAttribute : global::System.Attribute + { + } +} +"; + public static bool IsDerivedFrom(this INamedTypeSymbol type, INamedTypeSymbol baseType) { INamedTypeSymbol? t = type; diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index 955003893..1a36bd239 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -27,7 +27,7 @@ namespace ICSharpCode.Decompiler /// /// Settings for the decompiler. /// - public class DecompilerSettings : INotifyPropertyChanged + public partial class DecompilerSettings : INotifyPropertyChanged { /// /// Equivalent to new DecompilerSettings(LanguageVersion.Latest) @@ -50,378 +50,83 @@ namespace ICSharpCode.Decompiler SetLanguageVersion(languageVersion); } - /// - /// Deactivates all language features from versions newer than . - /// - public void SetLanguageVersion(CSharp.LanguageVersion languageVersion) - { - // By default, all decompiler features are enabled. - // Disable some of them based on language version: - if (languageVersion < CSharp.LanguageVersion.CSharp2) - { - anonymousMethods = false; - liftNullables = false; - yieldReturn = false; - useImplicitMethodGroupConversion = false; - useObjectCreationOfGenericTypeParameter = false; - } - if (languageVersion < CSharp.LanguageVersion.CSharp3) - { - anonymousTypes = false; - useLambdaSyntax = false; - objectCollectionInitializers = false; - automaticProperties = false; - extensionMethods = false; - queryExpressions = false; - expressionTrees = false; - } - if (languageVersion < CSharp.LanguageVersion.CSharp4) - { - dynamic = false; - namedArguments = false; - optionalArguments = false; - } - if (languageVersion < CSharp.LanguageVersion.CSharp5) - { - asyncAwait = false; - } - if (languageVersion < CSharp.LanguageVersion.CSharp6) - { - awaitInCatchFinally = false; - useExpressionBodyForCalculatedGetterOnlyProperties = false; - nullPropagation = false; - stringInterpolation = false; - dictionaryInitializers = false; - extensionMethodsInCollectionInitializers = false; - getterOnlyAutomaticProperties = false; - } - if (languageVersion < CSharp.LanguageVersion.CSharp7) - { - outVariables = false; - throwExpressions = false; - tupleTypes = false; - tupleConversions = false; - discards = false; - localFunctions = false; - deconstruction = false; - patternMatching = false; - useRefLocalsForAccurateOrderOfEvaluation = false; - } - if (languageVersion < CSharp.LanguageVersion.CSharp7_2) - { - introduceReadonlyAndInModifiers = false; - introduceRefModifiersOnStructs = false; - nonTrailingNamedArguments = false; - refExtensionMethods = false; - introducePrivateProtectedAccessibilty = false; - } - if (languageVersion < CSharp.LanguageVersion.CSharp7_3) - { - introduceUnmanagedConstraint = false; - stackAllocInitializers = false; - tupleComparisons = false; - patternBasedFixedStatement = false; - } - if (languageVersion < CSharp.LanguageVersion.CSharp8_0) - { - nullableReferenceTypes = false; - readOnlyMethods = false; - asyncUsingAndForEachStatement = false; - asyncEnumerator = false; - useEnhancedUsing = false; - staticLocalFunctions = false; - ranges = false; - switchExpressions = false; - recursivePatternMatching = false; - } - if (languageVersion < CSharp.LanguageVersion.CSharp9_0) - { - nativeIntegers = false; - initAccessors = false; - functionPointers = false; - forEachWithGetEnumeratorExtension = false; - recordClasses = false; - withExpressions = false; - usePrimaryConstructorSyntax = false; - covariantReturns = false; - relationalPatterns = false; - patternCombinators = false; - } - if (languageVersion < CSharp.LanguageVersion.CSharp10_0) - { - fileScopedNamespaces = false; - recordStructs = false; - structDefaultConstructorsAndFieldInitializers = false; - } - if (languageVersion < CSharp.LanguageVersion.CSharp11_0) - { - scopedRef = false; - requiredMembers = false; - numericIntPtr = false; - utf8StringLiterals = false; - unsignedRightShift = false; - checkedOperators = false; - } - if (languageVersion < CSharp.LanguageVersion.CSharp12_0) - { - refReadOnlyParameters = false; - usePrimaryConstructorSyntaxForNonRecordTypes = false; - inlineArrays = false; - } - if (languageVersion < CSharp.LanguageVersion.CSharp13_0) - { - paramsCollections = false; - } - if (languageVersion < CSharp.LanguageVersion.CSharp14_0) - { - extensionMembers = false; - firstClassSpanTypes = false; - } - } - - public CSharp.LanguageVersion GetMinimumRequiredVersion() - { - if (extensionMembers || firstClassSpanTypes) - return CSharp.LanguageVersion.CSharp14_0; - if (paramsCollections) - return CSharp.LanguageVersion.CSharp13_0; - if (refReadOnlyParameters || usePrimaryConstructorSyntaxForNonRecordTypes || inlineArrays) - return CSharp.LanguageVersion.CSharp12_0; - if (scopedRef || requiredMembers || numericIntPtr || utf8StringLiterals || unsignedRightShift || checkedOperators) - return CSharp.LanguageVersion.CSharp11_0; - if (fileScopedNamespaces || recordStructs || structDefaultConstructorsAndFieldInitializers) - return CSharp.LanguageVersion.CSharp10_0; - if (nativeIntegers || initAccessors || functionPointers || forEachWithGetEnumeratorExtension - || recordClasses || withExpressions || usePrimaryConstructorSyntax || covariantReturns - || relationalPatterns || patternCombinators) - return CSharp.LanguageVersion.CSharp9_0; - if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator || asyncUsingAndForEachStatement - || staticLocalFunctions || ranges || switchExpressions || recursivePatternMatching) - return CSharp.LanguageVersion.CSharp8_0; - if (introduceUnmanagedConstraint || tupleComparisons || stackAllocInitializers - || patternBasedFixedStatement) - return CSharp.LanguageVersion.CSharp7_3; - if (introduceRefModifiersOnStructs || introduceReadonlyAndInModifiers - || nonTrailingNamedArguments || refExtensionMethods || introducePrivateProtectedAccessibilty) - return CSharp.LanguageVersion.CSharp7_2; - // C# 7.1 missing - if (outVariables || throwExpressions || tupleTypes || tupleConversions - || discards || localFunctions || deconstruction || patternMatching || useRefLocalsForAccurateOrderOfEvaluation) - return CSharp.LanguageVersion.CSharp7; - if (awaitInCatchFinally || useExpressionBodyForCalculatedGetterOnlyProperties || nullPropagation - || stringInterpolation || dictionaryInitializers || extensionMethodsInCollectionInitializers - || getterOnlyAutomaticProperties) - return CSharp.LanguageVersion.CSharp6; - if (asyncAwait) - return CSharp.LanguageVersion.CSharp5; - if (dynamic || namedArguments || optionalArguments) - return CSharp.LanguageVersion.CSharp4; - if (anonymousTypes || objectCollectionInitializers || automaticProperties - || queryExpressions || expressionTrees) - return CSharp.LanguageVersion.CSharp3; - if (anonymousMethods || liftNullables || yieldReturn || useImplicitMethodGroupConversion || useObjectCreationOfGenericTypeParameter) - return CSharp.LanguageVersion.CSharp2; - return CSharp.LanguageVersion.CSharp1; - } - - bool nativeIntegers = true; - /// /// Use C# 9 nint/nuint types. /// - [Category("C# 9.0 / VS 2019.8")] [Description("DecompilerSettings.NativeIntegers")] - public bool NativeIntegers { - get { return nativeIntegers; } - set { - if (nativeIntegers != value) - { - nativeIntegers = value; - OnPropertyChanged(); - } - } - } - - bool numericIntPtr = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp9_0)] + public partial bool NativeIntegers { get; set; } /// /// Treat IntPtr/UIntPtr as nint/nuint. /// - [Category("C# 11.0 / VS 2022.4")] [Description("DecompilerSettings.NumericIntPtr")] - public bool NumericIntPtr { - get { return numericIntPtr; } - set { - if (numericIntPtr != value) - { - numericIntPtr = value; - OnPropertyChanged(); - } - } - } - - bool covariantReturns = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp11_0)] + public partial bool NumericIntPtr { get; set; } /// /// Decompile C# 9 covariant return types. /// - [Category("C# 9.0 / VS 2019.8")] [Description("DecompilerSettings.CovariantReturns")] - public bool CovariantReturns { - get { return covariantReturns; } - set { - if (covariantReturns != value) - { - covariantReturns = value; - OnPropertyChanged(); - } - } - } - - bool initAccessors = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp9_0)] + public partial bool CovariantReturns { get; set; } /// /// Use C# 9 init; property accessors. /// - [Category("C# 9.0 / VS 2019.8")] [Description("DecompilerSettings.InitAccessors")] - public bool InitAccessors { - get { return initAccessors; } - set { - if (initAccessors != value) - { - initAccessors = value; - OnPropertyChanged(); - } - } - } - - bool recordClasses = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp9_0)] + public partial bool InitAccessors { get; set; } /// /// Use C# 9 record classes. /// - [Category("C# 9.0 / VS 2019.8")] [Description("DecompilerSettings.RecordClasses")] - public bool RecordClasses { - get { return recordClasses; } - set { - if (recordClasses != value) - { - recordClasses = value; - OnPropertyChanged(); - } - } - } - - bool recordStructs = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp9_0)] + public partial bool RecordClasses { get; set; } /// /// Use C# 10 record structs. /// - [Category("C# 10.0 / VS 2022")] [Description("DecompilerSettings.RecordStructs")] - public bool RecordStructs { - get { return recordStructs; } - set { - if (recordStructs != value) - { - recordStructs = value; - OnPropertyChanged(); - } - } - } - - bool structDefaultConstructorsAndFieldInitializers = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp10_0)] + public partial bool RecordStructs { get; set; } /// /// Use field initializers in structs. /// - [Category("C# 10.0 / VS 2022")] [Description("DecompilerSettings.StructDefaultConstructorsAndFieldInitializers")] - public bool StructDefaultConstructorsAndFieldInitializers { - get { return structDefaultConstructorsAndFieldInitializers; } - set { - if (structDefaultConstructorsAndFieldInitializers != value) - { - structDefaultConstructorsAndFieldInitializers = value; - OnPropertyChanged(); - } - } - } - - bool withExpressions = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp10_0)] + public partial bool StructDefaultConstructorsAndFieldInitializers { get; set; } /// /// Use C# 9 with initializer expressions. /// - [Category("C# 9.0 / VS 2019.8")] [Description("DecompilerSettings.WithExpressions")] - public bool WithExpressions { - get { return withExpressions; } - set { - if (withExpressions != value) - { - withExpressions = value; - OnPropertyChanged(); - } - } - } - - bool usePrimaryConstructorSyntax = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp9_0)] + public partial bool WithExpressions { get; set; } /// /// Use primary constructor syntax with records. /// - [Category("C# 9.0 / VS 2019.8")] [Description("DecompilerSettings.UsePrimaryConstructorSyntax")] - public bool UsePrimaryConstructorSyntax { - get { return usePrimaryConstructorSyntax; } - set { - if (usePrimaryConstructorSyntax != value) - { - usePrimaryConstructorSyntax = value; - OnPropertyChanged(); - } - } - } - - bool functionPointers = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp9_0)] + public partial bool UsePrimaryConstructorSyntax { get; set; } /// /// Use C# 9 delegate* unmanaged types. /// If this option is disabled, function pointers will instead be decompiled with type `IntPtr`. /// - [Category("C# 9.0 / VS 2019.8")] [Description("DecompilerSettings.FunctionPointers")] - public bool FunctionPointers { - get { return functionPointers; } - set { - if (functionPointers != value) - { - functionPointers = value; - OnPropertyChanged(); - } - } - } - - bool scopedRef = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp9_0)] + public partial bool FunctionPointers { get; set; } /// /// Use C# 11 scoped modifier. /// - [Category("C# 11.0 / VS 2022.4")] [Description("DecompilerSettings.ScopedRef")] - public bool ScopedRef { - get { return scopedRef; } - set { - if (scopedRef != value) - { - scopedRef = value; - OnPropertyChanged(); - } - } - } + [DecompilerSetting(CSharp.LanguageVersion.CSharp11_0)] + public partial bool ScopedRef { get; set; } [Obsolete("Renamed to ScopedRef. This property will be removed in a future version of the decompiler.")] [Browsable(false)] @@ -430,610 +135,245 @@ namespace ICSharpCode.Decompiler set { ScopedRef = value; } } - 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; + [DecompilerSetting(CSharp.LanguageVersion.CSharp11_0)] + public partial bool RequiredMembers { get; set; } /// /// Use C# 8 switch expressions. /// - [Category("C# 8.0 / VS 2019")] [Description("DecompilerSettings.SwitchExpressions")] - public bool SwitchExpressions { - get { return switchExpressions; } - set { - if (switchExpressions != value) - { - switchExpressions = value; - OnPropertyChanged(); - } - } - } - - bool fileScopedNamespaces = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp8_0)] + public partial bool SwitchExpressions { get; set; } /// /// Use C# 10 file-scoped namespaces. /// - [Category("C# 10.0 / VS 2022")] [Description("DecompilerSettings.FileScopedNamespaces")] - public bool FileScopedNamespaces { - get { return fileScopedNamespaces; } - set { - if (fileScopedNamespaces != value) - { - fileScopedNamespaces = value; - OnPropertyChanged(); - } - } - } - - bool anonymousMethods = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp10_0)] + public partial bool FileScopedNamespaces { get; set; } /// /// Decompile anonymous methods/lambdas. /// - [Category("C# 2.0 / VS 2005")] [Description("DecompilerSettings.DecompileAnonymousMethodsLambdas")] - public bool AnonymousMethods { - get { return anonymousMethods; } - set { - if (anonymousMethods != value) - { - anonymousMethods = value; - OnPropertyChanged(); - } - } - } - - bool anonymousTypes = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp2)] + public partial bool AnonymousMethods { get; set; } /// /// Decompile anonymous types. /// - [Category("C# 3.0 / VS 2008")] [Description("DecompilerSettings.DecompileAnonymousTypes")] - public bool AnonymousTypes { - get { return anonymousTypes; } - set { - if (anonymousTypes != value) - { - anonymousTypes = value; - OnPropertyChanged(); - } - } - } - - bool useLambdaSyntax = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp3)] + public partial bool AnonymousTypes { get; set; } /// /// Use C# 3 lambda syntax if possible. /// - [Category("C# 3.0 / VS 2008")] [Description("DecompilerSettings.UseLambdaSyntaxIfPossible")] - public bool UseLambdaSyntax { - get { return useLambdaSyntax; } - set { - if (useLambdaSyntax != value) - { - useLambdaSyntax = value; - OnPropertyChanged(); - } - } - } - - bool expressionTrees = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp3, AffectsMinimumRequiredVersion = false)] + public partial bool UseLambdaSyntax { get; set; } /// /// Decompile expression trees. /// - [Category("C# 3.0 / VS 2008")] [Description("DecompilerSettings.DecompileExpressionTrees")] - public bool ExpressionTrees { - get { return expressionTrees; } - set { - if (expressionTrees != value) - { - expressionTrees = value; - OnPropertyChanged(); - } - } - } - - bool yieldReturn = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp3)] + public partial bool ExpressionTrees { get; set; } /// /// Decompile enumerators. /// - [Category("C# 2.0 / VS 2005")] [Description("DecompilerSettings.DecompileEnumeratorsYieldReturn")] - public bool YieldReturn { - get { return yieldReturn; } - set { - if (yieldReturn != value) - { - yieldReturn = value; - OnPropertyChanged(); - } - } - } - - bool dynamic = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp2)] + public partial bool YieldReturn { get; set; } /// /// Decompile use of the 'dynamic' type. /// - [Category("C# 4.0 / VS 2010")] [Description("DecompilerSettings.DecompileUseOfTheDynamicType")] - public bool Dynamic { - get { return dynamic; } - set { - if (dynamic != value) - { - dynamic = value; - OnPropertyChanged(); - } - } - } - - bool asyncAwait = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp4)] + public partial bool Dynamic { get; set; } /// /// Decompile async methods. /// - [Category("C# 5.0 / VS 2012")] [Description("DecompilerSettings.DecompileAsyncMethods")] - public bool AsyncAwait { - get { return asyncAwait; } - set { - if (asyncAwait != value) - { - asyncAwait = value; - OnPropertyChanged(); - } - } - } - - bool awaitInCatchFinally = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp5)] + public partial bool AsyncAwait { get; set; } /// /// Decompile await in catch/finally blocks. /// Only has an effect if is enabled. /// - [Category("C# 6.0 / VS 2015")] [Description("DecompilerSettings.DecompileAwaitInCatchFinallyBlocks")] - public bool AwaitInCatchFinally { - get { return awaitInCatchFinally; } - set { - if (awaitInCatchFinally != value) - { - awaitInCatchFinally = value; - OnPropertyChanged(); - } - } - } - - bool asyncEnumerator = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp6)] + public partial bool AwaitInCatchFinally { get; set; } /// /// Decompile IAsyncEnumerator/IAsyncEnumerable. /// Only has an effect if is enabled. /// - [Category("C# 8.0 / VS 2019")] [Description("DecompilerSettings.AsyncEnumerator")] - public bool AsyncEnumerator { - get { return asyncEnumerator; } - set { - if (asyncEnumerator != value) - { - asyncEnumerator = value; - OnPropertyChanged(); - } - } - } - - bool decimalConstants = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp8_0)] + public partial bool AsyncEnumerator { get; set; } /// /// Decompile [DecimalConstant(...)] as simple literal values. /// [Category("C# 1.0 / VS .NET")] [Description("DecompilerSettings.DecompileDecimalConstantAsSimpleLiteralValues")] - public bool DecimalConstants { - get { return decimalConstants; } - set { - if (decimalConstants != value) - { - decimalConstants = value; - OnPropertyChanged(); - } - } - } - - bool fixedBuffers = true; + [DecompilerSetting] + public partial bool DecimalConstants { get; set; } /// /// Decompile C# 1.0 'public unsafe fixed int arr[10];' members. /// [Category("C# 1.0 / VS .NET")] [Description("DecompilerSettings.DecompileC10PublicUnsafeFixedIntArr10Members")] - public bool FixedBuffers { - get { return fixedBuffers; } - set { - if (fixedBuffers != value) - { - fixedBuffers = value; - OnPropertyChanged(); - } - } - } - - bool stringConcat = true; + [DecompilerSetting] + public partial bool FixedBuffers { get; set; } /// /// Decompile 'string.Concat(a, b)' calls into 'a + b'. /// [Category("C# 1.0 / VS .NET")] [Description("DecompilerSettings.StringConcat")] - public bool StringConcat { - get { return stringConcat; } - set { - if (stringConcat != value) - { - stringConcat = value; - OnPropertyChanged(); - } - } - } - - bool liftNullables = true; + [DecompilerSetting] + public partial bool StringConcat { get; set; } /// /// Use lifted operators for nullables. /// - [Category("C# 2.0 / VS 2005")] [Description("DecompilerSettings.UseLiftedOperatorsForNullables")] - public bool LiftNullables { - get { return liftNullables; } - set { - if (liftNullables != value) - { - liftNullables = value; - OnPropertyChanged(); - } - } - } - - bool nullPropagation = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp2)] + public partial bool LiftNullables { get; set; } /// /// Decompile C# 6 ?. and ?[] operators. /// - [Category("C# 6.0 / VS 2015")] [Description("DecompilerSettings.NullPropagation")] - public bool NullPropagation { - get { return nullPropagation; } - set { - if (nullPropagation != value) - { - nullPropagation = value; - OnPropertyChanged(); - } - } - } - - bool automaticProperties = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp6)] + public partial bool NullPropagation { get; set; } /// /// Decompile automatic properties /// - [Category("C# 3.0 / VS 2008")] [Description("DecompilerSettings.DecompileAutomaticProperties")] - public bool AutomaticProperties { - get { return automaticProperties; } - set { - if (automaticProperties != value) - { - automaticProperties = value; - OnPropertyChanged(); - } - } - } - - bool getterOnlyAutomaticProperties = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp3)] + public partial bool AutomaticProperties { get; set; } /// /// Decompile getter-only automatic properties /// - [Category("C# 6.0 / VS 2015")] [Description("DecompilerSettings.GetterOnlyAutomaticProperties")] - public bool GetterOnlyAutomaticProperties { - get { return getterOnlyAutomaticProperties; } - set { - if (getterOnlyAutomaticProperties != value) - { - getterOnlyAutomaticProperties = value; - OnPropertyChanged(); - } - } - } - - bool automaticEvents = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp6)] + public partial bool GetterOnlyAutomaticProperties { get; set; } /// /// Decompile automatic events /// [Category("C# 1.0 / VS .NET")] [Description("DecompilerSettings.DecompileAutomaticEvents")] - public bool AutomaticEvents { - get { return automaticEvents; } - set { - if (automaticEvents != value) - { - automaticEvents = value; - OnPropertyChanged(); - } - } - } - - bool usingStatement = true; + [DecompilerSetting] + public partial bool AutomaticEvents { get; set; } /// /// Decompile using statements. /// [Category("C# 1.0 / VS .NET")] [Description("DecompilerSettings.DetectUsingStatements")] - public bool UsingStatement { - get { return usingStatement; } - set { - if (usingStatement != value) - { - usingStatement = value; - OnPropertyChanged(); - } - } - } - - bool useEnhancedUsing = true; + [DecompilerSetting] + public partial bool UsingStatement { get; set; } /// /// Use enhanced using statements. /// - [Category("C# 8.0 / VS 2019")] [Description("DecompilerSettings.UseEnhancedUsing")] - public bool UseEnhancedUsing { - get { return useEnhancedUsing; } - set { - if (useEnhancedUsing != value) - { - useEnhancedUsing = value; - OnPropertyChanged(); - } - } - } - - bool alwaysUseBraces = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp8_0, AffectsMinimumRequiredVersion = false)] + public partial bool UseEnhancedUsing { get; set; } /// /// Gets/Sets whether to use braces for single-statement-blocks. /// [Category("DecompilerSettings.Other")] [Description("DecompilerSettings.AlwaysUseBraces")] - public bool AlwaysUseBraces { - get { return alwaysUseBraces; } - set { - if (alwaysUseBraces != value) - { - alwaysUseBraces = value; - OnPropertyChanged(); - } - } - } - - bool forEachStatement = true; + [DecompilerSetting] + public partial bool AlwaysUseBraces { get; set; } /// /// Decompile foreach statements. /// [Category("C# 1.0 / VS .NET")] [Description("DecompilerSettings.DetectForeachStatements")] - public bool ForEachStatement { - get { return forEachStatement; } - set { - if (forEachStatement != value) - { - forEachStatement = value; - OnPropertyChanged(); - } - } - } - - bool forEachWithGetEnumeratorExtension = true; + [DecompilerSetting] + public partial bool ForEachStatement { get; set; } /// /// Support GetEnumerator extension methods in foreach. /// - [Category("C# 9.0 / VS 2019.8")] [Description("DecompilerSettings.DecompileForEachWithGetEnumeratorExtension")] - public bool ForEachWithGetEnumeratorExtension { - get { return forEachWithGetEnumeratorExtension; } - set { - if (forEachWithGetEnumeratorExtension != value) - { - forEachWithGetEnumeratorExtension = value; - OnPropertyChanged(); - } - } - } - - bool paramsCollections = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp9_0)] + public partial bool ForEachWithGetEnumeratorExtension { get; set; } /// /// Support params collections. /// - [Category("C# 13.0 / VS 2022.12")] [Description("DecompilerSettings.DecompileParamsCollections")] - public bool ParamsCollections { - get { return paramsCollections; } - set { - if (paramsCollections != value) - { - paramsCollections = value; - OnPropertyChanged(); - } - } - } - - bool lockStatement = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp13_0)] + public partial bool ParamsCollections { get; set; } /// /// Decompile lock statements. /// [Category("C# 1.0 / VS .NET")] [Description("DecompilerSettings.DetectLockStatements")] - public bool LockStatement { - get { return lockStatement; } - set { - if (lockStatement != value) - { - lockStatement = value; - OnPropertyChanged(); - } - } - } - - bool switchStatementOnString = true; + [DecompilerSetting] + public partial bool LockStatement { get; set; } [Category("C# 1.0 / VS .NET")] [Description("DecompilerSettings.DetectSwitchOnString")] - public bool SwitchStatementOnString { - get { return switchStatementOnString; } - set { - if (switchStatementOnString != value) - { - switchStatementOnString = value; - OnPropertyChanged(); - } - } - } - - bool sparseIntegerSwitch = true; + [DecompilerSetting] + public partial bool SwitchStatementOnString { get; set; } [Category("C# 1.0 / VS .NET")] [Description("DecompilerSettings.SparseIntegerSwitch")] - public bool SparseIntegerSwitch { - get { return sparseIntegerSwitch; } - set { - if (sparseIntegerSwitch != value) - { - sparseIntegerSwitch = value; - OnPropertyChanged(); - } - } - } - - bool usingDeclarations = true; + [DecompilerSetting] + public partial bool SparseIntegerSwitch { get; set; } [Category("C# 1.0 / VS .NET")] [Description("DecompilerSettings.InsertUsingDeclarations")] - public bool UsingDeclarations { - get { return usingDeclarations; } - set { - if (usingDeclarations != value) - { - usingDeclarations = value; - OnPropertyChanged(); - } - } - } + [DecompilerSetting] + public partial bool UsingDeclarations { get; set; } - bool extensionMethods = true; - - [Category("C# 3.0 / VS 2008")] [Description("DecompilerSettings.UseExtensionMethodSyntax")] - public bool ExtensionMethods { - get { return extensionMethods; } - set { - if (extensionMethods != value) - { - extensionMethods = value; - OnPropertyChanged(); - } - } - } - - bool queryExpressions = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp3, AffectsMinimumRequiredVersion = false)] + public partial bool ExtensionMethods { get; set; } - [Category("C# 3.0 / VS 2008")] [Description("DecompilerSettings.UseLINQExpressionSyntax")] - public bool QueryExpressions { - get { return queryExpressions; } - set { - if (queryExpressions != value) - { - queryExpressions = value; - OnPropertyChanged(); - } - } - } - - bool useImplicitMethodGroupConversion = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp3)] + public partial bool QueryExpressions { get; set; } /// /// Gets/Sets whether to use C# 2.0 method group conversions. /// true: EventHandler h = this.OnClick; /// false: EventHandler h = new EventHandler(this.OnClick); /// - [Category("C# 2.0 / VS 2005")] [Description("DecompilerSettings.UseImplicitMethodGroupConversions")] - public bool UseImplicitMethodGroupConversion { - get { return useImplicitMethodGroupConversion; } - set { - if (useImplicitMethodGroupConversion != value) - { - useImplicitMethodGroupConversion = value; - OnPropertyChanged(); - } - } - } - - bool useObjectCreationOfGenericTypeParameter = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp2)] + public partial bool UseImplicitMethodGroupConversion { get; set; } /// /// Gets/Sets whether to use object creation expressions for generic types with new() constraint. /// true: T t = new T(); /// false: T t = Activator.CreateInstance<T>() /// - [Category("C# 2.0 / VS 2005")] [Description("DecompilerSettings.UseObjectCreationOfGenericTypeParameter")] - public bool UseObjectCreationOfGenericTypeParameter { - get { return useObjectCreationOfGenericTypeParameter; } - set { - if (useObjectCreationOfGenericTypeParameter != value) - { - useObjectCreationOfGenericTypeParameter = value; - OnPropertyChanged(); - } - } - } - - bool alwaysCastTargetsOfExplicitInterfaceImplementationCalls = false; + [DecompilerSetting(CSharp.LanguageVersion.CSharp2)] + public partial bool UseObjectCreationOfGenericTypeParameter { get; set; } /// /// Gets/Sets whether to always cast targets to explicitly implemented methods. @@ -1043,18 +383,8 @@ namespace ICSharpCode.Decompiler /// [Category("Other")] [Description("DecompilerSettings.AlwaysCastTargetsOfExplicitInterfaceImplementationCalls")] - public bool AlwaysCastTargetsOfExplicitInterfaceImplementationCalls { - get { return alwaysCastTargetsOfExplicitInterfaceImplementationCalls; } - set { - if (alwaysCastTargetsOfExplicitInterfaceImplementationCalls != value) - { - alwaysCastTargetsOfExplicitInterfaceImplementationCalls = value; - OnPropertyChanged(); - } - } - } - - bool alwaysQualifyMemberReferences = false; + [DecompilerSetting(DefaultValue = false)] + public partial bool AlwaysCastTargetsOfExplicitInterfaceImplementationCalls { get; set; } /// /// Gets/Sets whether to always qualify member references. @@ -1064,18 +394,8 @@ namespace ICSharpCode.Decompiler /// [Category("Other")] [Description("DecompilerSettings.AlwaysQualifyMemberReferences")] - public bool AlwaysQualifyMemberReferences { - get { return alwaysQualifyMemberReferences; } - set { - if (alwaysQualifyMemberReferences != value) - { - alwaysQualifyMemberReferences = value; - OnPropertyChanged(); - } - } - } - - bool alwaysShowEnumMemberValues = false; + [DecompilerSetting(DefaultValue = false)] + public partial bool AlwaysQualifyMemberReferences { get; set; } /// /// Gets/Sets whether to always show enum member values. @@ -1085,111 +405,48 @@ namespace ICSharpCode.Decompiler /// [Category("Other")] [Description("DecompilerSettings.AlwaysShowEnumMemberValues")] - public bool AlwaysShowEnumMemberValues { - get { return alwaysShowEnumMemberValues; } - set { - if (alwaysShowEnumMemberValues != value) - { - alwaysShowEnumMemberValues = value; - OnPropertyChanged(); - } - } - } - - bool useDebugSymbols = true; + [DecompilerSetting(DefaultValue = false)] + public partial bool AlwaysShowEnumMemberValues { get; set; } /// /// Gets/Sets whether to use variable names from debug symbols, if available. /// [Category("Other")] [Description("DecompilerSettings.UseVariableNamesFromDebugSymbolsIfAvailable")] - public bool UseDebugSymbols { - get { return useDebugSymbols; } - set { - if (useDebugSymbols != value) - { - useDebugSymbols = value; - OnPropertyChanged(); - } - } - } - - bool arrayInitializers = true; - - /// - /// Gets/Sets whether to use array initializers. - /// If set to false, might produce non-compilable code. - /// - [Category("C# 1.0 / VS .NET")] - [Description("DecompilerSettings.ArrayInitializerExpressions")] - public bool ArrayInitializers { - get { return arrayInitializers; } - set { - if (arrayInitializers != value) - { - arrayInitializers = value; - OnPropertyChanged(); - } - } - } + [DecompilerSetting] + public partial bool UseDebugSymbols { get; set; } - bool objectCollectionInitializers = true; + /// + /// Gets/Sets whether to use array initializers. + /// If set to false, might produce non-compilable code. + /// + [Category("C# 1.0 / VS .NET")] + [Description("DecompilerSettings.ArrayInitializerExpressions")] + [DecompilerSetting] + public partial bool ArrayInitializers { get; set; } /// /// Gets/Sets whether to use C# 3.0 object/collection initializers. /// - [Category("C# 3.0 / VS 2008")] [Description("DecompilerSettings.ObjectCollectionInitializerExpressions")] - public bool ObjectOrCollectionInitializers { - get { return objectCollectionInitializers; } - set { - if (objectCollectionInitializers != value) - { - objectCollectionInitializers = value; - OnPropertyChanged(); - } - } - } - - bool dictionaryInitializers = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp3)] + public partial bool ObjectOrCollectionInitializers { get; set; } /// /// Gets/Sets whether to use C# 6.0 dictionary initializers. /// Only has an effect if ObjectOrCollectionInitializers is enabled. /// - [Category("C# 6.0 / VS 2015")] [Description("DecompilerSettings.DictionaryInitializerExpressions")] - public bool DictionaryInitializers { - get { return dictionaryInitializers; } - set { - if (dictionaryInitializers != value) - { - dictionaryInitializers = value; - OnPropertyChanged(); - } - } - } - - bool extensionMethodsInCollectionInitializers = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp6)] + public partial bool DictionaryInitializers { get; set; } /// /// Gets/Sets whether to use C# 6.0 Extension Add methods in collection initializers. /// Only has an effect if ObjectOrCollectionInitializers is enabled. /// - [Category("C# 6.0 / VS 2015")] [Description("DecompilerSettings.AllowExtensionAddMethodsInCollectionInitializerExpressions")] - public bool ExtensionMethodsInCollectionInitializers { - get { return extensionMethodsInCollectionInitializers; } - set { - if (extensionMethodsInCollectionInitializers != value) - { - extensionMethodsInCollectionInitializers = value; - OnPropertyChanged(); - } - } - } - - bool useRefLocalsForAccurateOrderOfEvaluation = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp6)] + public partial bool ExtensionMethodsInCollectionInitializers { get; set; } /// /// Gets/Sets whether to use local ref variables in cases where this is necessary @@ -1198,744 +455,291 @@ namespace ICSharpCode.Decompiler /// order of evaluation. /// See https://github.com/icsharpcode/ILSpy/issues/2050 /// - [Category("C# 7.0 / VS 2017")] [Description("DecompilerSettings.UseRefLocalsForAccurateOrderOfEvaluation")] - public bool UseRefLocalsForAccurateOrderOfEvaluation { - get { return useRefLocalsForAccurateOrderOfEvaluation; } - set { - if (useRefLocalsForAccurateOrderOfEvaluation != value) - { - useRefLocalsForAccurateOrderOfEvaluation = value; - OnPropertyChanged(); - } - } - } - - bool refExtensionMethods = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp7)] + public partial bool UseRefLocalsForAccurateOrderOfEvaluation { get; set; } /// /// Gets/Sets whether to use C# 7.2 'ref' extension methods. /// - [Category("C# 7.2 / VS 2017.4")] [Description("DecompilerSettings.AllowExtensionMethodSyntaxOnRef")] - public bool RefExtensionMethods { - get { return refExtensionMethods; } - set { - if (refExtensionMethods != value) - { - refExtensionMethods = value; - OnPropertyChanged(); - } - } - } - - bool stringInterpolation = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp7_2)] + public partial bool RefExtensionMethods { get; set; } /// /// Gets/Sets whether to use C# 6.0 string interpolation /// - [Category("C# 6.0 / VS 2015")] [Description("DecompilerSettings.UseStringInterpolation")] - public bool StringInterpolation { - get { return stringInterpolation; } - set { - if (stringInterpolation != value) - { - stringInterpolation = value; - OnPropertyChanged(); - } - } - } - - bool utf8StringLiterals = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp6)] + public partial bool StringInterpolation { get; set; } /// /// Gets/Sets whether to use C# 11.0 UTF-8 string literals /// - [Category("C# 11.0 / VS 2022.4")] [Description("DecompilerSettings.Utf8StringLiterals")] - public bool Utf8StringLiterals { - get { return utf8StringLiterals; } - set { - if (utf8StringLiterals != value) - { - utf8StringLiterals = value; - OnPropertyChanged(); - } - } - } - - bool switchOnReadOnlySpanChar = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp11_0)] + public partial bool Utf8StringLiterals { get; set; } /// /// Gets/Sets whether to use C# 11.0 switch on (ReadOnly)Span<char> /// [Category("C# 11.0 / VS 2022.4")] [Description("DecompilerSettings.SwitchOnReadOnlySpanChar")] - public bool SwitchOnReadOnlySpanChar { - get { return switchOnReadOnlySpanChar; } - set { - if (switchOnReadOnlySpanChar != value) - { - switchOnReadOnlySpanChar = value; - OnPropertyChanged(); - } - } - } - - bool unsignedRightShift = true; + [DecompilerSetting] + public partial bool SwitchOnReadOnlySpanChar { get; set; } /// /// Gets/Sets whether to use C# 11.0 unsigned right shift operator. /// - [Category("C# 11.0 / VS 2022.4")] [Description("DecompilerSettings.UnsignedRightShift")] - public bool UnsignedRightShift { - get { return unsignedRightShift; } - set { - if (unsignedRightShift != value) - { - unsignedRightShift = value; - OnPropertyChanged(); - } - } - } - - bool checkedOperators = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp11_0)] + public partial bool UnsignedRightShift { get; set; } /// /// Gets/Sets whether to use C# 11.0 user-defined checked operators. /// - [Category("C# 11.0 / VS 2022.4")] [Description("DecompilerSettings.CheckedOperators")] - public bool CheckedOperators { - get { return checkedOperators; } - set { - if (checkedOperators != value) - { - checkedOperators = value; - OnPropertyChanged(); - } - } - } - - bool showXmlDocumentation = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp11_0)] + public partial bool CheckedOperators { get; set; } /// /// Gets/Sets whether to include XML documentation comments in the decompiled code. /// [Category("DecompilerSettings.Other")] [Description("DecompilerSettings.IncludeXMLDocumentationCommentsInTheDecompiledCode")] - public bool ShowXmlDocumentation { - get { return showXmlDocumentation; } - set { - if (showXmlDocumentation != value) - { - showXmlDocumentation = value; - OnPropertyChanged(); - } - } - } - - bool foldBraces = false; + [DecompilerSetting] + public partial bool ShowXmlDocumentation { get; set; } [Browsable(false)] - public bool FoldBraces { - get { return foldBraces; } - set { - if (foldBraces != value) - { - foldBraces = value; - OnPropertyChanged(); - } - } - } - - bool expandXmlDocumentationComments = false; + [DecompilerSetting(DefaultValue = false)] + public partial bool FoldBraces { get; set; } [Browsable(false)] - public bool ExpandXmlDocumentationComments { - get { return expandXmlDocumentationComments; } - set { - if (expandXmlDocumentationComments != value) - { - expandXmlDocumentationComments = value; - OnPropertyChanged(); - } - } - } - - bool expandMemberDefinitions = false; + [DecompilerSetting(DefaultValue = false)] + public partial bool ExpandXmlDocumentationComments { get; set; } [Browsable(false)] - public bool ExpandMemberDefinitions { - get { return expandMemberDefinitions; } - set { - if (expandMemberDefinitions != value) - { - expandMemberDefinitions = value; - OnPropertyChanged(); - } - } - } - - bool expandUsingDeclarations = false; + [DecompilerSetting(DefaultValue = false)] + public partial bool ExpandMemberDefinitions { get; set; } [Browsable(false)] - public bool ExpandUsingDeclarations { - get { return expandUsingDeclarations; } - set { - if (expandUsingDeclarations != value) - { - expandUsingDeclarations = value; - OnPropertyChanged(); - } - } - } - - bool decompileMemberBodies = true; + [DecompilerSetting(DefaultValue = false)] + public partial bool ExpandUsingDeclarations { get; set; } /// /// Gets/Sets whether member bodies should be decompiled. /// [Category("DecompilerSettings.Other")] [Browsable(false)] - public bool DecompileMemberBodies { - get { return decompileMemberBodies; } - set { - if (decompileMemberBodies != value) - { - decompileMemberBodies = value; - OnPropertyChanged(); - } - } - } - - bool useExpressionBodyForCalculatedGetterOnlyProperties = true; + [DecompilerSetting] + public partial bool DecompileMemberBodies { get; set; } /// /// Gets/Sets whether simple calculated getter-only property declarations /// should use expression body syntax. /// - [Category("C# 6.0 / VS 2015")] [Description("DecompilerSettings.UseExpressionBodiedMemberSyntaxForGetOnlyProperties")] - public bool UseExpressionBodyForCalculatedGetterOnlyProperties { - get { return useExpressionBodyForCalculatedGetterOnlyProperties; } - set { - if (useExpressionBodyForCalculatedGetterOnlyProperties != value) - { - useExpressionBodyForCalculatedGetterOnlyProperties = value; - OnPropertyChanged(); - } - } - } - - bool outVariables = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp6)] + public partial bool UseExpressionBodyForCalculatedGetterOnlyProperties { get; set; } /// /// Gets/Sets whether out variable declarations should be used when possible. /// - [Category("C# 7.0 / VS 2017")] [Description("DecompilerSettings.UseOutVariableDeclarations")] - public bool OutVariables { - get { return outVariables; } - set { - if (outVariables != value) - { - outVariables = value; - OnPropertyChanged(); - } - } - } - - bool discards = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp7)] + public partial bool OutVariables { get; set; } /// /// Gets/Sets whether discards should be used when possible. /// Only has an effect if is enabled. /// - [Category("C# 7.0 / VS 2017")] [Description("DecompilerSettings.UseDiscards")] - public bool Discards { - get { return discards; } - set { - if (discards != value) - { - discards = value; - OnPropertyChanged(); - } - } - } - - bool introduceRefModifiersOnStructs = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp7)] + public partial bool Discards { get; set; } /// /// Gets/Sets whether IsByRefLikeAttribute should be replaced with 'ref' modifiers on structs. /// - [Category("C# 7.2 / VS 2017.4")] [Description("DecompilerSettings.IsByRefLikeAttributeShouldBeReplacedWithRefModifiersOnStructs")] - public bool IntroduceRefModifiersOnStructs { - get { return introduceRefModifiersOnStructs; } - set { - if (introduceRefModifiersOnStructs != value) - { - introduceRefModifiersOnStructs = value; - OnPropertyChanged(); - } - } - } - - bool introduceReadonlyAndInModifiers = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp7_2)] + public partial bool IntroduceRefModifiersOnStructs { get; set; } /// /// Gets/Sets whether IsReadOnlyAttribute should be replaced with 'readonly' modifiers on structs /// and with the 'in' modifier on parameters. /// - [Category("C# 7.2 / VS 2017.4")] [Description("DecompilerSettings." + "IsReadOnlyAttributeShouldBeReplacedWithReadonlyInModifiersOnStructsParameters")] - public bool IntroduceReadonlyAndInModifiers { - get { return introduceReadonlyAndInModifiers; } - set { - if (introduceReadonlyAndInModifiers != value) - { - introduceReadonlyAndInModifiers = value; - OnPropertyChanged(); - } - } - } - - bool introducePrivateProtectedAccessibilty = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp7_2)] + public partial bool IntroduceReadonlyAndInModifiers { get; set; } /// /// Gets/Sets whether "private protected" should be used. /// - [Category("C# 7.2 / VS 2017.4")] [Description("DecompilerSettings.IntroducePrivateProtectedAccessibility")] - public bool IntroducePrivateProtectedAccessibility { - get { return introducePrivateProtectedAccessibilty; } - set { - if (introducePrivateProtectedAccessibilty != value) - { - introducePrivateProtectedAccessibilty = value; - OnPropertyChanged(); - } - } - } + [DecompilerSetting(CSharp.LanguageVersion.CSharp7_2)] + public partial bool IntroducePrivateProtectedAccessibility { get; set; } - bool readOnlyMethods = true; - - [Category("C# 8.0 / VS 2019")] [Description("DecompilerSettings.ReadOnlyMethods")] - public bool ReadOnlyMethods { - get { return readOnlyMethods; } - set { - if (readOnlyMethods != value) - { - readOnlyMethods = value; - OnPropertyChanged(); - } - } - } - - bool asyncUsingAndForEachStatement = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp8_0)] + public partial bool ReadOnlyMethods { get; set; } - [Category("C# 8.0 / VS 2019")] [Description("DecompilerSettings.DetectAsyncUsingAndForeachStatements")] - public bool AsyncUsingAndForEachStatement { - get { return asyncUsingAndForEachStatement; } - set { - if (asyncUsingAndForEachStatement != value) - { - asyncUsingAndForEachStatement = value; - OnPropertyChanged(); - } - } - } - - bool introduceUnmanagedConstraint = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp8_0)] + public partial bool AsyncUsingAndForEachStatement { get; set; } /// /// If this option is active, [IsUnmanagedAttribute] on type parameters /// is replaced with "T : unmanaged" constraints. /// - [Category("C# 7.3 / VS 2017.7")] [Description("DecompilerSettings." + "IsUnmanagedAttributeOnTypeParametersShouldBeReplacedWithUnmanagedConstraints")] - public bool IntroduceUnmanagedConstraint { - get { return introduceUnmanagedConstraint; } - set { - if (introduceUnmanagedConstraint != value) - { - introduceUnmanagedConstraint = value; - OnPropertyChanged(); - } - } - } - - bool stackAllocInitializers = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp7_3)] + public partial bool IntroduceUnmanagedConstraint { get; set; } /// /// Gets/Sets whether C# 7.3 stackalloc initializers should be used. /// - [Category("C# 7.3 / VS 2017.7")] [Description("DecompilerSettings.UseStackallocInitializerSyntax")] - public bool StackAllocInitializers { - get { return stackAllocInitializers; } - set { - if (stackAllocInitializers != value) - { - stackAllocInitializers = value; - OnPropertyChanged(); - } - } - } - - bool patternBasedFixedStatement = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp7_3)] + public partial bool StackAllocInitializers { get; set; } /// /// Gets/Sets whether C# 7.3 pattern based fixed statement should be used. /// - [Category("C# 7.3 / VS 2017.7")] [Description("DecompilerSettings.UsePatternBasedFixedStatement")] - public bool PatternBasedFixedStatement { - get { return patternBasedFixedStatement; } - set { - if (patternBasedFixedStatement != value) - { - patternBasedFixedStatement = value; - OnPropertyChanged(); - } - } - } - - bool tupleTypes = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp7_3)] + public partial bool PatternBasedFixedStatement { get; set; } /// /// Gets/Sets whether tuple type syntax (int, string) /// should be used for System.ValueTuple. /// - [Category("C# 7.0 / VS 2017")] [Description("DecompilerSettings.UseTupleTypeSyntax")] - public bool TupleTypes { - get { return tupleTypes; } - set { - if (tupleTypes != value) - { - tupleTypes = value; - OnPropertyChanged(); - } - } - } - - bool throwExpressions = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp7)] + public partial bool TupleTypes { get; set; } /// /// Gets/Sets whether throw expressions should be used. /// - [Category("C# 7.0 / VS 2017")] [Description("DecompilerSettings.UseThrowExpressions")] - public bool ThrowExpressions { - get { return throwExpressions; } - set { - if (throwExpressions != value) - { - throwExpressions = value; - OnPropertyChanged(); - } - } - } - - bool tupleConversions = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp7)] + public partial bool ThrowExpressions { get; set; } /// /// Gets/Sets whether implicit conversions between tuples /// should be used in the decompiled output. /// - [Category("C# 7.0 / VS 2017")] [Description("DecompilerSettings.UseImplicitConversionsBetweenTupleTypes")] - public bool TupleConversions { - get { return tupleConversions; } - set { - if (tupleConversions != value) - { - tupleConversions = value; - OnPropertyChanged(); - } - } - } - - bool tupleComparisons = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp7)] + public partial bool TupleConversions { get; set; } /// /// Gets/Sets whether tuple comparisons should be detected. /// - [Category("C# 7.3 / VS 2017.7")] [Description("DecompilerSettings.DetectTupleComparisons")] - public bool TupleComparisons { - get { return tupleComparisons; } - set { - if (tupleComparisons != value) - { - tupleComparisons = value; - OnPropertyChanged(); - } - } - } - - bool namedArguments = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp7_3)] + public partial bool TupleComparisons { get; set; } /// /// Gets/Sets whether named arguments should be used. /// - [Category("C# 4.0 / VS 2010")] [Description("DecompilerSettings.UseNamedArguments")] - public bool NamedArguments { - get { return namedArguments; } - set { - if (namedArguments != value) - { - namedArguments = value; - OnPropertyChanged(); - } - } - } - - bool nonTrailingNamedArguments = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp4)] + public partial bool NamedArguments { get; set; } /// /// Gets/Sets whether C# 7.2 non-trailing named arguments should be used. /// - [Category("C# 7.2 / VS 2017.4")] [Description("DecompilerSettings.UseNonTrailingNamedArguments")] - public bool NonTrailingNamedArguments { - get { return nonTrailingNamedArguments; } - set { - if (nonTrailingNamedArguments != value) - { - nonTrailingNamedArguments = value; - OnPropertyChanged(); - } - } - } - - bool optionalArguments = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp7_2)] + public partial bool NonTrailingNamedArguments { get; set; } /// /// Gets/Sets whether optional arguments should be removed, if possible. /// - [Category("C# 4.0 / VS 2010")] [Description("DecompilerSettings.RemoveOptionalArgumentsIfPossible")] - public bool OptionalArguments { - get { return optionalArguments; } - set { - if (optionalArguments != value) - { - optionalArguments = value; - OnPropertyChanged(); - } - } - } - - bool expandParamsArguments = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp4)] + public partial bool OptionalArguments { get; set; } /// /// Gets/Sets whether to expand params arguments by replacing explicit array creation /// with individual values in method calls. /// - [Category("C# 1.0 / VS .NET")] - [Description("DecompilerSettings.ExpandParamsArguments")] - public bool ExpandParamsArguments { - get { return expandParamsArguments; } - set { - if (expandParamsArguments != value) - { - expandParamsArguments = value; - OnPropertyChanged(); - } - } - } - - bool localFunctions = true; + [Category("C# 1.0 / VS .NET")] + [Description("DecompilerSettings.ExpandParamsArguments")] + [DecompilerSetting] + public partial bool ExpandParamsArguments { get; set; } /// /// Gets/Sets whether C# 7.0 local functions should be transformed. /// - [Category("C# 7.0 / VS 2017")] [Description("DecompilerSettings.IntroduceLocalFunctions")] - public bool LocalFunctions { - get { return localFunctions; } - set { - if (localFunctions != value) - { - localFunctions = value; - OnPropertyChanged(); - } - } - } - - bool deconstruction = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp7)] + public partial bool LocalFunctions { get; set; } /// /// Gets/Sets whether C# 7.0 deconstruction should be detected. /// - [Category("C# 7.0 / VS 2017")] [Description("DecompilerSettings.Deconstruction")] - public bool Deconstruction { - get { return deconstruction; } - set { - if (deconstruction != value) - { - deconstruction = value; - OnPropertyChanged(); - } - } - } - - bool patternMatching = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp7)] + public partial bool Deconstruction { get; set; } /// /// Gets/Sets whether C# 7.0 pattern matching should be detected. /// - [Category("C# 7.0 / VS 2017")] [Description("DecompilerSettings.PatternMatching")] - public bool PatternMatching { - get { return patternMatching; } - set { - if (patternMatching != value) - { - patternMatching = value; - OnPropertyChanged(); - } - } - } - - bool recursivePatternMatching = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp7)] + public partial bool PatternMatching { get; set; } /// /// Gets/Sets whether C# 8.0 recursive patterns should be detected. /// - [Category("C# 8.0 / VS 2019")] [Description("DecompilerSettings.RecursivePatternMatching")] - public bool RecursivePatternMatching { - get { return recursivePatternMatching; } - set { - if (recursivePatternMatching != value) - { - recursivePatternMatching = value; - OnPropertyChanged(); - } - } - } - - bool patternCombinators = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp8_0)] + public partial bool RecursivePatternMatching { get; set; } /// /// Gets/Sets whether C# 9.0 and, or, not patterns should be detected. /// - [Category("C# 9.0 / VS 2019.8")] [Description("DecompilerSettings.PatternCombinators")] - public bool PatternCombinators { - get { return patternCombinators; } - set { - if (patternCombinators != value) - { - patternCombinators = value; - OnPropertyChanged(); - } - } - } - - bool relationalPatterns = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp9_0)] + public partial bool PatternCombinators { get; set; } /// /// Gets/Sets whether C# 9.0 relational patterns should be detected. /// - [Category("C# 9.0 / VS 2019.8")] [Description("DecompilerSettings.RelationalPatterns")] - public bool RelationalPatterns { - get { return relationalPatterns; } - set { - if (relationalPatterns != value) - { - relationalPatterns = value; - OnPropertyChanged(); - } - } - } - - bool staticLocalFunctions = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp9_0)] + public partial bool RelationalPatterns { get; set; } /// /// Gets/Sets whether C# 8.0 static local functions should be transformed. /// - [Category("C# 8.0 / VS 2019")] [Description("DecompilerSettings.IntroduceStaticLocalFunctions")] - public bool StaticLocalFunctions { - get { return staticLocalFunctions; } - set { - if (staticLocalFunctions != value) - { - staticLocalFunctions = value; - OnPropertyChanged(); - } - } - } - - bool ranges = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp8_0)] + public partial bool StaticLocalFunctions { get; set; } /// /// Gets/Sets whether C# 8.0 index and range syntax should be used. /// - [Category("C# 8.0 / VS 2019")] [Description("DecompilerSettings.Ranges")] - public bool Ranges { - get { return ranges; } - set { - if (ranges != value) - { - ranges = value; - OnPropertyChanged(); - } - } - } - - bool nullableReferenceTypes = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp8_0)] + public partial bool Ranges { get; set; } /// /// Gets/Sets whether C# 8.0 nullable reference types are enabled. /// - [Category("C# 8.0 / VS 2019")] [Description("DecompilerSettings.NullableReferenceTypes")] - public bool NullableReferenceTypes { - get { return nullableReferenceTypes; } - set { - if (nullableReferenceTypes != value) - { - nullableReferenceTypes = value; - OnPropertyChanged(); - } - } - } - - bool showDebugInfo; + [DecompilerSetting(CSharp.LanguageVersion.CSharp8_0)] + public partial bool NullableReferenceTypes { get; set; } [Category("DecompilerSettings.Other")] [Description("DecompilerSettings.ShowInfoFromDebugSymbolsIfAvailable")] [Browsable(false)] - public bool ShowDebugInfo { - get { return showDebugInfo; } - set { - if (showDebugInfo != value) - { - showDebugInfo = value; - OnPropertyChanged(); - } - } - } - + [DecompilerSetting(DefaultValue = false)] + public partial bool ShowDebugInfo { get; set; } #region Options to aid VB decompilation - bool assumeArrayLengthFitsIntoInt32 = true; /// /// Gets/Sets whether the decompiler can assume that 'ldlen; conv.i4.ovf' @@ -1943,276 +747,108 @@ namespace ICSharpCode.Decompiler /// [Category("DecompilerSettings.VBSpecificOptions")] [Browsable(false)] - public bool AssumeArrayLengthFitsIntoInt32 { - get { return assumeArrayLengthFitsIntoInt32; } - set { - if (assumeArrayLengthFitsIntoInt32 != value) - { - assumeArrayLengthFitsIntoInt32 = value; - OnPropertyChanged(); - } - } - } - - bool introduceIncrementAndDecrement = true; + [DecompilerSetting] + public partial bool AssumeArrayLengthFitsIntoInt32 { get; set; } /// /// Gets/Sets whether to use increment and decrement operators /// [Category("DecompilerSettings.VBSpecificOptions")] [Browsable(false)] - public bool IntroduceIncrementAndDecrement { - get { return introduceIncrementAndDecrement; } - set { - if (introduceIncrementAndDecrement != value) - { - introduceIncrementAndDecrement = value; - OnPropertyChanged(); - } - } - } - - bool makeAssignmentExpressions = true; + [DecompilerSetting] + public partial bool IntroduceIncrementAndDecrement { get; set; } /// /// Gets/Sets whether to use assignment expressions such as in while ((count = Do()) != 0) ; /// [Category("DecompilerSettings.VBSpecificOptions")] [Browsable(false)] - public bool MakeAssignmentExpressions { - get { return makeAssignmentExpressions; } - set { - if (makeAssignmentExpressions != value) - { - makeAssignmentExpressions = value; - OnPropertyChanged(); - } - } - } - + [DecompilerSetting] + public partial bool MakeAssignmentExpressions { get; set; } #endregion - #region Options to aid F# decompilation - bool removeDeadCode = false; [Category("DecompilerSettings.FSpecificOptions")] [Description("DecompilerSettings.RemoveDeadAndSideEffectFreeCodeUseWithCaution")] - public bool RemoveDeadCode { - get { return removeDeadCode; } - set { - if (removeDeadCode != value) - { - removeDeadCode = value; - OnPropertyChanged(); - } - } - } - - bool removeDeadStores = false; + [DecompilerSetting(DefaultValue = false)] + public partial bool RemoveDeadCode { get; set; } [Category("DecompilerSettings.FSpecificOptions")] [Description("DecompilerSettings.RemoveDeadStores")] - public bool RemoveDeadStores { - get { return removeDeadStores; } - set { - if (removeDeadStores != value) - { - removeDeadStores = value; - OnPropertyChanged(); - } - } - } + [DecompilerSetting(DefaultValue = false)] + public partial bool RemoveDeadStores { get; set; } #endregion - #region Assembly Load and Resolve options - bool loadInMemory = false; - [Browsable(false)] - public bool LoadInMemory { - get { return loadInMemory; } - set { - if (loadInMemory != value) - { - loadInMemory = value; - OnPropertyChanged(); - } - } - } - - bool throwOnAssemblyResolveErrors = true; + [DecompilerSetting(DefaultValue = false)] + public partial bool LoadInMemory { get; set; } [Browsable(false)] - public bool ThrowOnAssemblyResolveErrors { - get { return throwOnAssemblyResolveErrors; } - set { - if (throwOnAssemblyResolveErrors != value) - { - throwOnAssemblyResolveErrors = value; - OnPropertyChanged(); - } - } - } - - bool applyWindowsRuntimeProjections = true; + [DecompilerSetting] + public partial bool ThrowOnAssemblyResolveErrors { get; set; } [Category("DecompilerSettings.Other")] [Description("DecompilerSettings.ApplyWindowsRuntimeProjectionsOnLoadedAssemblies")] - public bool ApplyWindowsRuntimeProjections { - get { return applyWindowsRuntimeProjections; } - set { - if (applyWindowsRuntimeProjections != value) - { - applyWindowsRuntimeProjections = value; - OnPropertyChanged(); - } - } - } - - bool autoLoadAssemblyReferences = true; + [DecompilerSetting] + public partial bool ApplyWindowsRuntimeProjections { get; set; } [Category("DecompilerSettings.Other")] [Description("DecompilerSettings.AutoLoadAssemblyReferences")] - public bool AutoLoadAssemblyReferences { - get { return autoLoadAssemblyReferences; } - set { - if (autoLoadAssemblyReferences != value) - { - autoLoadAssemblyReferences = value; - OnPropertyChanged(); - } - } - } - + [DecompilerSetting] + public partial bool AutoLoadAssemblyReferences { get; set; } #endregion - bool forStatement = true; - /// /// Gets/sets whether the decompiler should produce for loops. /// [Category("C# 1.0 / VS .NET")] [Description("DecompilerSettings.ForStatement")] - public bool ForStatement { - get { return forStatement; } - set { - if (forStatement != value) - { - forStatement = value; - OnPropertyChanged(); - } - } - } - - bool doWhileStatement = true; + [DecompilerSetting] + public partial bool ForStatement { get; set; } /// /// Gets/sets whether the decompiler should produce do-while loops. /// [Category("C# 1.0 / VS .NET")] [Description("DecompilerSettings.DoWhileStatement")] - public bool DoWhileStatement { - get { return doWhileStatement; } - set { - if (doWhileStatement != value) - { - doWhileStatement = value; - OnPropertyChanged(); - } - } - } - - bool refReadOnlyParameters = true; + [DecompilerSetting] + public partial bool DoWhileStatement { get; set; } /// /// Gets/sets whether RequiresLocationAttribute on parameters should be replaced with 'ref readonly' modifiers. /// - [Category("C# 12.0 / VS 2022.8")] [Description("DecompilerSettings.RefReadOnlyParameters")] - public bool RefReadOnlyParameters { - get { return refReadOnlyParameters; } - set { - if (refReadOnlyParameters != value) - { - refReadOnlyParameters = value; - OnPropertyChanged(); - } - } - } - - bool usePrimaryConstructorSyntaxForNonRecordTypes = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp12_0)] + public partial bool RefReadOnlyParameters { get; set; } /// /// Use primary constructor syntax with classes and structs. /// - [Category("C# 12.0 / VS 2022.8")] [Description("DecompilerSettings.UsePrimaryConstructorSyntaxForNonRecordTypes")] - public bool UsePrimaryConstructorSyntaxForNonRecordTypes { - get { return usePrimaryConstructorSyntaxForNonRecordTypes; } - set { - if (usePrimaryConstructorSyntaxForNonRecordTypes != value) - { - usePrimaryConstructorSyntaxForNonRecordTypes = value; - OnPropertyChanged(); - } - } - } - - bool inlineArrays = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp12_0)] + public partial bool UsePrimaryConstructorSyntaxForNonRecordTypes { get; set; } /// /// Gets/Sets whether C# 12.0 inline array uses should be transformed. /// - [Category("C# 12.0 / VS 2022.8")] [Description("DecompilerSettings.InlineArrays")] - public bool InlineArrays { - get { return inlineArrays; } - set { - if (inlineArrays != value) - { - inlineArrays = value; - OnPropertyChanged(); - } - } - } - - bool extensionMembers = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp12_0)] + public partial bool InlineArrays { get; set; } /// /// Gets/Sets whether C# 14.0 extension members should be transformed. /// - [Category("C# 14.0 / VS 2026")] [Description("DecompilerSettings.ExtensionMembers")] - public bool ExtensionMembers { - get { return extensionMembers; } - set { - if (extensionMembers != value) - { - extensionMembers = value; - OnPropertyChanged(); - } - } - } - - bool firstClassSpanTypes = true; + [DecompilerSetting(CSharp.LanguageVersion.CSharp14_0)] + public partial bool ExtensionMembers { get; set; } /// /// Gets/Sets whether (ReadOnly)Span<T> should be treated like built-in types. /// - [Category("C# 14.0 / VS 2026")] [Description("DecompilerSettings.FirstClassSpanTypes")] - public bool FirstClassSpanTypes { - get { return firstClassSpanTypes; } - set { - if (firstClassSpanTypes != value) - { - firstClassSpanTypes = value; - OnPropertyChanged(); - } - } - } - - bool separateLocalVariableDeclarations = false; + [DecompilerSetting(CSharp.LanguageVersion.CSharp14_0)] + public partial bool FirstClassSpanTypes { get; set; } /// /// Gets/sets whether the decompiler should separate local variable declarations @@ -2220,18 +856,8 @@ namespace ICSharpCode.Decompiler /// [Category("DecompilerSettings.Other")] [Description("DecompilerSettings.SeparateLocalVariableDeclarations")] - public bool SeparateLocalVariableDeclarations { - get { return separateLocalVariableDeclarations; } - set { - if (separateLocalVariableDeclarations != value) - { - separateLocalVariableDeclarations = value; - OnPropertyChanged(); - } - } - } - - bool useSdkStyleProjectFormat = true; + [DecompilerSetting(DefaultValue = false)] + public partial bool SeparateLocalVariableDeclarations { get; set; } /// /// Gets or sets a value indicating whether the new SDK style format @@ -2239,18 +865,8 @@ namespace ICSharpCode.Decompiler /// [Category("DecompilerSettings.ProjectExport")] [Description("DecompilerSettings.UseSdkStyleProjectFormat")] - public bool UseSdkStyleProjectFormat { - get { return useSdkStyleProjectFormat; } - set { - if (useSdkStyleProjectFormat != value) - { - useSdkStyleProjectFormat = value; - OnPropertyChanged(); - } - } - } - - bool useNestedDirectoriesForNamespaces; + [DecompilerSetting] + public partial bool UseSdkStyleProjectFormat { get; set; } /// /// Gets/sets whether namespaces and namespace-like identifiers should be split at '.' @@ -2258,18 +874,8 @@ namespace ICSharpCode.Decompiler /// [Category("DecompilerSettings.ProjectExport")] [Description("DecompilerSettings.UseNestedDirectoriesForNamespaces")] - public bool UseNestedDirectoriesForNamespaces { - get { return useNestedDirectoriesForNamespaces; } - set { - if (useNestedDirectoriesForNamespaces != value) - { - useNestedDirectoriesForNamespaces = value; - OnPropertyChanged(); - } - } - } - - bool aggressiveScalarReplacementOfAggregates = false; + [DecompilerSetting(DefaultValue = false)] + public partial bool UseNestedDirectoriesForNamespaces { get; set; } [Category("DecompilerSettings.Other")] [Description("DecompilerSettings.AggressiveScalarReplacementOfAggregates")] @@ -2277,18 +883,8 @@ namespace ICSharpCode.Decompiler #if !DEBUG [Browsable(false)] #endif - public bool AggressiveScalarReplacementOfAggregates { - get { return aggressiveScalarReplacementOfAggregates; } - set { - if (aggressiveScalarReplacementOfAggregates != value) - { - aggressiveScalarReplacementOfAggregates = value; - OnPropertyChanged(); - } - } - } - - bool aggressiveInlining = false; + [DecompilerSetting(DefaultValue = false)] + public partial bool AggressiveScalarReplacementOfAggregates { get; set; } /// /// If set to false (the default), the decompiler will inline local variables only when they occur @@ -2297,36 +893,16 @@ namespace ICSharpCode.Decompiler /// [Category("DecompilerSettings.Other")] [Description("DecompilerSettings.AggressiveInlining")] - public bool AggressiveInlining { - get { return aggressiveInlining; } - set { - if (aggressiveInlining != value) - { - aggressiveInlining = value; - OnPropertyChanged(); - } - } - } - - bool alwaysUseGlobal = false; + [DecompilerSetting(DefaultValue = false)] + public partial bool AggressiveInlining { get; set; } /// /// Always fully qualify namespaces using the "global::" prefix. /// [Category("DecompilerSettings.Other")] [Description("DecompilerSettings.AlwaysUseGlobal")] - public bool AlwaysUseGlobal { - get { return alwaysUseGlobal; } - set { - if (alwaysUseGlobal != value) - { - alwaysUseGlobal = value; - OnPropertyChanged(); - } - } - } - - bool alwaysMoveInitializer = false; + [DecompilerSetting(DefaultValue = false)] + public partial bool AlwaysUseGlobal { get; set; } /// /// If set to false (the default), the decompiler will move field initializers at the start of constructors @@ -2336,36 +912,16 @@ namespace ICSharpCode.Decompiler /// [Category("DecompilerSettings.Other")] [Description("DecompilerSettings.AlwaysMoveInitializer")] - public bool AlwaysMoveInitializer { - get { return alwaysMoveInitializer; } - set { - if (alwaysMoveInitializer != value) - { - alwaysMoveInitializer = value; - OnPropertyChanged(); - } - } - } - - bool sortCustomAttributes = false; + [DecompilerSetting(DefaultValue = false)] + public partial bool AlwaysMoveInitializer { get; set; } /// /// Sort custom attributes. /// [Category("DecompilerSettings.Other")] [Description("DecompilerSettings.SortCustomAttributes")] - public bool SortCustomAttributes { - get { return sortCustomAttributes; } - set { - if (sortCustomAttributes != value) - { - sortCustomAttributes = value; - OnPropertyChanged(); - } - } - } - - bool sortSwitchSections = false; + [DecompilerSetting(DefaultValue = false)] + public partial bool SortCustomAttributes { get; set; } /// /// Sort switch sections by their label value instead of by IL offset. @@ -2374,34 +930,16 @@ namespace ICSharpCode.Decompiler /// [Category("DecompilerSettings.Other")] [Description("DecompilerSettings.SortSwitchSections")] - public bool SortSwitchSections { - get { return sortSwitchSections; } - set { - if (sortSwitchSections != value) - { - sortSwitchSections = value; - OnPropertyChanged(); - } - } - } - - bool checkForOverflowUnderflow = false; + [DecompilerSetting(DefaultValue = false)] + public partial bool SortSwitchSections { get; set; } /// /// Check for overflow and underflow in operators. /// [Category("DecompilerSettings.Other")] [Description("DecompilerSettings.CheckForOverflowUnderflow")] - public bool CheckForOverflowUnderflow { - get { return checkForOverflowUnderflow; } - set { - if (checkForOverflowUnderflow != value) - { - checkForOverflowUnderflow = value; - OnPropertyChanged(); - } - } - } + [DecompilerSetting(DefaultValue = false)] + public partial bool CheckForOverflowUnderflow { get; set; } CSharpFormattingOptions csharpFormattingOptions; From 401530b86dbd98a647be5b3c32733a4200df1f34 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 8 Aug 2026 14:49:23 +0200 Subject: [PATCH 2/8] Let ExtensionMethods, UseLambdaSyntax and UseEnhancedUsing raise the minimum version These three settings were disabled by SetLanguageVersion for older targets but, unlike every comparable syntax-preference setting, never raised GetMinimumRequiredVersion while enabled - an omission that had gone unnoticed in the handwritten version bookkeeping. Drop the AffectsMinimumRequiredVersion escape hatch that reproduced it. Assisted-by: Claude:claude-fable-5:Claude Code --- .../DecompilerSettingsGenerator.cs | 17 ++--------------- ICSharpCode.Decompiler/DecompilerSettings.cs | 6 +++--- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/ICSharpCode.Decompiler.Generators/DecompilerSettingsGenerator.cs b/ICSharpCode.Decompiler.Generators/DecompilerSettingsGenerator.cs index 99e87e4b5..9784c2602 100644 --- a/ICSharpCode.Decompiler.Generators/DecompilerSettingsGenerator.cs +++ b/ICSharpCode.Decompiler.Generators/DecompilerSettingsGenerator.cs @@ -98,7 +98,7 @@ internal class DecompilerSettingsGenerator : IIncrementalGenerator readonly record struct SettingInfo( string Namespace, string ClassName, string Accessibility, string PropertyName, string FieldName, - bool DefaultValue, int VersionValue, string? VersionName, string? Category, bool AffectsMinimumRequiredVersion, + bool DefaultValue, int VersionValue, string? VersionName, string? Category, string FilePath, int SpanStart); // A diagnostic captured during the transform; kept as plain values so the pipeline stays cacheable. @@ -122,12 +122,6 @@ namespace ICSharpCode.Decompiler /// Initial value of the setting. Defaults to true. public bool DefaultValue { get; set; } = true; - - /// - /// Whether enabling the setting raises GetMinimumRequiredVersion() to the version the - /// setting was introduced in. Defaults to true; only meaningful on version-gated settings. - /// - public bool AffectsMinimumRequiredVersion { get; set; } = true; } } @@ -199,7 +193,6 @@ namespace ICSharpCode.Decompiler } bool defaultValue = true; - bool affectsMinimumRequiredVersion = true; foreach (var named in attribute.NamedArguments) { // A named argument that failed to bind is already a compiler error; ignore it here. @@ -207,8 +200,6 @@ namespace ICSharpCode.Decompiler continue; if (named.Key == "DefaultValue") defaultValue = namedValue; - else if (named.Key == "AffectsMinimumRequiredVersion") - affectsMinimumRequiredVersion = namedValue; } string fieldName = char.ToLowerInvariant(property.Name[0]) + property.Name.Substring(1); @@ -225,7 +216,6 @@ namespace ICSharpCode.Decompiler versionValue, versionName, category, - affectsMinimumRequiredVersion, node.SyntaxTree.FilePath, node.SpanStart); return new SettingResult(setting, diagnostics.Count == 0 ? null : diagnostics.ToEquatableArray()); @@ -373,10 +363,7 @@ namespace ICSharpCode.Decompiler builder.AppendLine("\t\t{"); foreach (var bucket in versionBuckets.Reverse()) { - var fields = bucket.Where(s => s.AffectsMinimumRequiredVersion).Select(s => s.FieldName).ToArray(); - if (fields.Length == 0) - continue; - builder.AppendLine($"\t\t\tif ({string.Join(" || ", fields)})"); + builder.AppendLine($"\t\t\tif ({string.Join(" || ", bucket.Select(s => s.FieldName))})"); builder.AppendLine($"\t\t\t\treturn global::ICSharpCode.Decompiler.CSharp.LanguageVersion.{bucket.First().VersionName};"); } builder.AppendLine("\t\t\treturn global::ICSharpCode.Decompiler.CSharp.LanguageVersion.CSharp1;"); diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index 1a36bd239..a8f6322ca 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -174,7 +174,7 @@ namespace ICSharpCode.Decompiler /// Use C# 3 lambda syntax if possible. /// [Description("DecompilerSettings.UseLambdaSyntaxIfPossible")] - [DecompilerSetting(CSharp.LanguageVersion.CSharp3, AffectsMinimumRequiredVersion = false)] + [DecompilerSetting(CSharp.LanguageVersion.CSharp3)] public partial bool UseLambdaSyntax { get; set; } /// @@ -293,7 +293,7 @@ namespace ICSharpCode.Decompiler /// Use enhanced using statements. /// [Description("DecompilerSettings.UseEnhancedUsing")] - [DecompilerSetting(CSharp.LanguageVersion.CSharp8_0, AffectsMinimumRequiredVersion = false)] + [DecompilerSetting(CSharp.LanguageVersion.CSharp8_0)] public partial bool UseEnhancedUsing { get; set; } /// @@ -350,7 +350,7 @@ namespace ICSharpCode.Decompiler public partial bool UsingDeclarations { get; set; } [Description("DecompilerSettings.UseExtensionMethodSyntax")] - [DecompilerSetting(CSharp.LanguageVersion.CSharp3, AffectsMinimumRequiredVersion = false)] + [DecompilerSetting(CSharp.LanguageVersion.CSharp3)] public partial bool ExtensionMethods { get; set; } [Description("DecompilerSettings.UseLINQExpressionSyntax")] From 0d0e88a61bb2ec7ce76e8990dd3833b9870f7d96 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 8 Aug 2026 14:53:46 +0200 Subject: [PATCH 3/8] Gate SwitchOnReadOnlySpanChar on C# 11.0 The setting carried the C# 11.0 display category but was missing from both SetLanguageVersion and GetMinimumRequiredVersion, so decompiling for an older target language version could still produce switches over ReadOnlySpan that the requested compiler cannot compile. Gating it like the other C# 11.0 settings closes that gap; the category string is now derived from the version like everywhere else. Assisted-by: Claude:claude-fable-5:Claude Code --- ICSharpCode.Decompiler/DecompilerSettings.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index a8f6322ca..79112cb98 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -483,9 +483,8 @@ namespace ICSharpCode.Decompiler /// /// Gets/Sets whether to use C# 11.0 switch on (ReadOnly)Span<char> /// - [Category("C# 11.0 / VS 2022.4")] [Description("DecompilerSettings.SwitchOnReadOnlySpanChar")] - [DecompilerSetting] + [DecompilerSetting(CSharp.LanguageVersion.CSharp11_0)] public partial bool SwitchOnReadOnlySpanChar { get; set; } /// From f39cd0d006a95c2b278ad327d3314520e7ab377f Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 8 Aug 2026 14:54:30 +0200 Subject: [PATCH 4/8] Use the DecompilerSettings.Other resource key consistently Four settings used the bare category string "Other" while the rest of the group uses the "DecompilerSettings.Other" resource key. Both happen to resolve to the same English text today, so the options UI shows one group, but the two keys would split into separate groups the moment their translations diverge. Assisted-by: Claude:claude-fable-5:Claude Code --- ICSharpCode.Decompiler/DecompilerSettings.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index 79112cb98..3691fd18d 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -381,7 +381,7 @@ namespace ICSharpCode.Decompiler /// false: pictureBox1.BeginInit(); /// default: false /// - [Category("Other")] + [Category("DecompilerSettings.Other")] [Description("DecompilerSettings.AlwaysCastTargetsOfExplicitInterfaceImplementationCalls")] [DecompilerSetting(DefaultValue = false)] public partial bool AlwaysCastTargetsOfExplicitInterfaceImplementationCalls { get; set; } @@ -392,7 +392,7 @@ namespace ICSharpCode.Decompiler /// false: DoSomething(); /// default: false /// - [Category("Other")] + [Category("DecompilerSettings.Other")] [Description("DecompilerSettings.AlwaysQualifyMemberReferences")] [DecompilerSetting(DefaultValue = false)] public partial bool AlwaysQualifyMemberReferences { get; set; } @@ -403,7 +403,7 @@ namespace ICSharpCode.Decompiler /// false: enum Kind { A, B, C = 5 } /// default: false /// - [Category("Other")] + [Category("DecompilerSettings.Other")] [Description("DecompilerSettings.AlwaysShowEnumMemberValues")] [DecompilerSetting(DefaultValue = false)] public partial bool AlwaysShowEnumMemberValues { get; set; } @@ -411,7 +411,7 @@ namespace ICSharpCode.Decompiler /// /// Gets/Sets whether to use variable names from debug symbols, if available. /// - [Category("Other")] + [Category("DecompilerSettings.Other")] [Description("DecompilerSettings.UseVariableNamesFromDebugSymbolsIfAvailable")] [DecompilerSetting] public partial bool UseDebugSymbols { get; set; } From b3bf04ebd91d8ca3391c0ea26746934290565b84 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 9 Aug 2026 11:30:28 +0200 Subject: [PATCH 5/8] Re-validate the explicit LanguageVersion when project export starts The LanguageVersion setter's InvalidOperationException is a safety net against exporting a project whose LangVersion cannot compile the emitted code, but it only fires at assignment time: Settings is mutable and shared, so enabling a feature after assigning the version slipped past the check. Re-validating at the start of DecompileProject closes that gap while keeping the setter's immediate feedback. Assisted-by: Claude:claude-fable-5:Claude Code --- .../WholeProjectDecompiler.cs | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs index c6ce3eada..86ffcf200 100644 --- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs @@ -63,14 +63,21 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler public LanguageVersion LanguageVersion { get { return languageVersion ?? Settings.GetMinimumRequiredVersion(); } set { - var minVersion = Settings.GetMinimumRequiredVersion(); - if (value < minVersion) - throw new InvalidOperationException($"The chosen settings require at least {minVersion}." + - $" Please change the DecompilerSettings accordingly."); + ValidateLanguageVersion(value); languageVersion = value; } } + void ValidateLanguageVersion(LanguageVersion version) + { + var minVersion = Settings.GetMinimumRequiredVersion(); + if (version < minVersion) + { + throw new InvalidOperationException($"The chosen settings require at least {minVersion}." + + " Please change the DecompilerSettings accordingly."); + } + } + bool IProjectInfoProvider.CheckForOverflowUnderflow => Settings.CheckForOverflowUnderflow; public IAssemblyResolver AssemblyResolver { get; } @@ -154,6 +161,14 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler { throw new InvalidOperationException("Must set TargetDirectory"); } + // The LanguageVersion setter already rejects a version below what the settings require, + // but Settings is mutable and shared, so re-validate against the settings actually in + // effect now - otherwise the exported project would carry a LangVersion under which the + // emitted code cannot compile. + if (languageVersion is { } explicitVersion) + { + ValidateLanguageVersion(explicitVersion); + } DecompilerEventSource.Log.ProjectDecompilationStart(file.Name); int codeFileCount = 0, resourceFileCount = 0; try From 590793fd215681d72d3b82ca49380716640b421b Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 9 Aug 2026 12:21:28 +0200 Subject: [PATCH 6/8] Document the two roles of the language version in the settings API The language version appears in two places that share a name but not a concept, which repeatedly reads as one confused API: on DecompilerSettings it is a construction shortcut (SetLanguageVersion initializes the feature flags once and the version is not stored, so the flags are the only state and the call is deliberately one-way), while on WholeProjectDecompiler it is an export parameter (the LangVersion stamped into the project file, defaulting to GetMinimumRequiredVersion() and rejected below it as a safety net against exporting uncompilable projects). Spell both roles out in the XML docs so the distinction no longer has to be reverse-engineered. Assisted-by: Claude:claude-fable-5:Claude Code --- .../DecompilerSettingsGenerator.cs | 13 +++++-------- .../WholeProjectDecompiler.cs | 7 +++++++ ICSharpCode.Decompiler/DecompilerSettings.cs | 19 +++++++++++++++++++ 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/ICSharpCode.Decompiler.Generators/DecompilerSettingsGenerator.cs b/ICSharpCode.Decompiler.Generators/DecompilerSettingsGenerator.cs index 9784c2602..96f6a7258 100644 --- a/ICSharpCode.Decompiler.Generators/DecompilerSettingsGenerator.cs +++ b/ICSharpCode.Decompiler.Generators/DecompilerSettingsGenerator.cs @@ -332,12 +332,12 @@ namespace ICSharpCode.Decompiler context.AddSource(hintName, SourceText.From(builder.ToString().Replace("\r\n", "\n"), Encoding.UTF8)); } + // Emitted as partial implementing declarations: the containing class supplies the defining + // stubs, which is where the XML documentation lives (the docs on a partial method's defining + // declaration apply as long as the implementation carries none). static void WriteSetLanguageVersion(StringBuilder builder, IGrouping[] versionBuckets) { - builder.AppendLine("\t\t/// "); - builder.AppendLine("\t\t/// Deactivates all language features from versions newer than ."); - builder.AppendLine("\t\t/// "); - builder.AppendLine("\t\tpublic void SetLanguageVersion(global::ICSharpCode.Decompiler.CSharp.LanguageVersion languageVersion)"); + builder.AppendLine("\t\tpublic partial void SetLanguageVersion(global::ICSharpCode.Decompiler.CSharp.LanguageVersion languageVersion)"); builder.AppendLine("\t\t{"); builder.AppendLine("\t\t\t// By default, all decompiler features are enabled."); builder.AppendLine("\t\t\t// Disable some of them based on language version:"); @@ -356,10 +356,7 @@ namespace ICSharpCode.Decompiler static void WriteGetMinimumRequiredVersion(StringBuilder builder, IGrouping[] versionBuckets) { - builder.AppendLine("\t\t/// "); - builder.AppendLine("\t\t/// Gets the lowest language version that includes all currently enabled language features."); - builder.AppendLine("\t\t/// "); - builder.AppendLine("\t\tpublic global::ICSharpCode.Decompiler.CSharp.LanguageVersion GetMinimumRequiredVersion()"); + builder.AppendLine("\t\tpublic partial global::ICSharpCode.Decompiler.CSharp.LanguageVersion GetMinimumRequiredVersion()"); builder.AppendLine("\t\t{"); foreach (var bucket in versionBuckets.Reverse()) { diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs index 86ffcf200..84f85968e 100644 --- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs @@ -60,6 +60,13 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler LanguageVersion? languageVersion; + /// + /// The C# language version written into the exported project file as LangVersion. + /// This is an export parameter, not decompiler state: when not set explicitly, it defaults + /// to of the current settings, + /// and an explicit value below that minimum is rejected (here and again when the export + /// starts) because the emitted code could not compile under it. + /// public LanguageVersion LanguageVersion { get { return languageVersion ?? Settings.GetMinimumRequiredVersion(); } set { diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index 3691fd18d..edb9ad7ca 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -44,12 +44,31 @@ namespace ICSharpCode.Decompiler /// This does not imply that the resulting code strictly uses only language features from /// that version. Language constructs like generics or ref locals cannot be removed from /// the compiled code. + /// The language version is a construction shortcut, not state: it initializes the feature + /// flags once (see ) and is not stored afterwards. /// public DecompilerSettings(CSharp.LanguageVersion languageVersion) { SetLanguageVersion(languageVersion); } + /// + /// One-shot profile initializer: deactivates all language features from versions newer than + /// . The version itself is not stored - the feature flags + /// are the only state - so the call is not reversible and a later call with a higher version + /// does not re-enable features. Use to derive a + /// version back from the flags. + /// + public partial void SetLanguageVersion(CSharp.LanguageVersion languageVersion); + + /// + /// Derives the lowest language version that includes all currently enabled language + /// features. The settings do not store a language version, so this derivation is how a + /// version is recovered from the flags; project export uses it as the default (and lower + /// bound) for the LangVersion written into the project file. + /// + public partial CSharp.LanguageVersion GetMinimumRequiredVersion(); + /// /// Use C# 9 nint/nuint types. /// From 08b12055d0447e5d3e72359e755a7144d2f2cd0c Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 9 Aug 2026 17:11:27 +0200 Subject: [PATCH 7/8] Derive the C# 1.0 setting categories from the version map The 14 C# 1.0 settings each carried a handwritten [Category("C# 1.0 / VS .NET")] literal, duplicating the per-version display knowledge the generator's CategoryByVersion map single-sources. Gating them on LanguageVersion.CSharp1 instead is observably identical: CSharp1 is the smallest enum value, so the generated SetLanguageVersion bucket can never fire, and the new GetMinimumRequiredVersion arm returns the same CSharp1 the final fallback already does. Assisted-by: Claude:claude-fable-5:Claude Code --- ICSharpCode.Decompiler/DecompilerSettings.cs | 42 +++++++------------- 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index edb9ad7ca..31082c624 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -243,25 +243,22 @@ namespace ICSharpCode.Decompiler /// /// Decompile [DecimalConstant(...)] as simple literal values. /// - [Category("C# 1.0 / VS .NET")] [Description("DecompilerSettings.DecompileDecimalConstantAsSimpleLiteralValues")] - [DecompilerSetting] + [DecompilerSetting(CSharp.LanguageVersion.CSharp1)] public partial bool DecimalConstants { get; set; } /// /// Decompile C# 1.0 'public unsafe fixed int arr[10];' members. /// - [Category("C# 1.0 / VS .NET")] [Description("DecompilerSettings.DecompileC10PublicUnsafeFixedIntArr10Members")] - [DecompilerSetting] + [DecompilerSetting(CSharp.LanguageVersion.CSharp1)] public partial bool FixedBuffers { get; set; } /// /// Decompile 'string.Concat(a, b)' calls into 'a + b'. /// - [Category("C# 1.0 / VS .NET")] [Description("DecompilerSettings.StringConcat")] - [DecompilerSetting] + [DecompilerSetting(CSharp.LanguageVersion.CSharp1)] public partial bool StringConcat { get; set; } /// @@ -295,17 +292,15 @@ namespace ICSharpCode.Decompiler /// /// Decompile automatic events /// - [Category("C# 1.0 / VS .NET")] [Description("DecompilerSettings.DecompileAutomaticEvents")] - [DecompilerSetting] + [DecompilerSetting(CSharp.LanguageVersion.CSharp1)] public partial bool AutomaticEvents { get; set; } /// /// Decompile using statements. /// - [Category("C# 1.0 / VS .NET")] [Description("DecompilerSettings.DetectUsingStatements")] - [DecompilerSetting] + [DecompilerSetting(CSharp.LanguageVersion.CSharp1)] public partial bool UsingStatement { get; set; } /// @@ -326,9 +321,8 @@ namespace ICSharpCode.Decompiler /// /// Decompile foreach statements. /// - [Category("C# 1.0 / VS .NET")] [Description("DecompilerSettings.DetectForeachStatements")] - [DecompilerSetting] + [DecompilerSetting(CSharp.LanguageVersion.CSharp1)] public partial bool ForEachStatement { get; set; } /// @@ -348,24 +342,20 @@ namespace ICSharpCode.Decompiler /// /// Decompile lock statements. /// - [Category("C# 1.0 / VS .NET")] [Description("DecompilerSettings.DetectLockStatements")] - [DecompilerSetting] + [DecompilerSetting(CSharp.LanguageVersion.CSharp1)] public partial bool LockStatement { get; set; } - [Category("C# 1.0 / VS .NET")] [Description("DecompilerSettings.DetectSwitchOnString")] - [DecompilerSetting] + [DecompilerSetting(CSharp.LanguageVersion.CSharp1)] public partial bool SwitchStatementOnString { get; set; } - [Category("C# 1.0 / VS .NET")] [Description("DecompilerSettings.SparseIntegerSwitch")] - [DecompilerSetting] + [DecompilerSetting(CSharp.LanguageVersion.CSharp1)] public partial bool SparseIntegerSwitch { get; set; } - [Category("C# 1.0 / VS .NET")] [Description("DecompilerSettings.InsertUsingDeclarations")] - [DecompilerSetting] + [DecompilerSetting(CSharp.LanguageVersion.CSharp1)] public partial bool UsingDeclarations { get; set; } [Description("DecompilerSettings.UseExtensionMethodSyntax")] @@ -439,9 +429,8 @@ namespace ICSharpCode.Decompiler /// Gets/Sets whether to use array initializers. /// If set to false, might produce non-compilable code. /// - [Category("C# 1.0 / VS .NET")] [Description("DecompilerSettings.ArrayInitializerExpressions")] - [DecompilerSetting] + [DecompilerSetting(CSharp.LanguageVersion.CSharp1)] public partial bool ArrayInitializers { get; set; } /// @@ -684,9 +673,8 @@ namespace ICSharpCode.Decompiler /// Gets/Sets whether to expand params arguments by replacing explicit array creation /// with individual values in method calls. /// - [Category("C# 1.0 / VS .NET")] [Description("DecompilerSettings.ExpandParamsArguments")] - [DecompilerSetting] + [DecompilerSetting(CSharp.LanguageVersion.CSharp1)] public partial bool ExpandParamsArguments { get; set; } /// @@ -820,17 +808,15 @@ namespace ICSharpCode.Decompiler /// /// Gets/sets whether the decompiler should produce for loops. /// - [Category("C# 1.0 / VS .NET")] [Description("DecompilerSettings.ForStatement")] - [DecompilerSetting] + [DecompilerSetting(CSharp.LanguageVersion.CSharp1)] public partial bool ForStatement { get; set; } /// /// Gets/sets whether the decompiler should produce do-while loops. /// - [Category("C# 1.0 / VS .NET")] [Description("DecompilerSettings.DoWhileStatement")] - [DecompilerSetting] + [DecompilerSetting(CSharp.LanguageVersion.CSharp1)] public partial bool DoWhileStatement { get; set; } /// From 7a7cb44a21e0bd7bc7b827359b61eb8a7c7ac9ac Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 9 Aug 2026 18:01:52 +0200 Subject: [PATCH 8/8] Pin the GetMinimumRequiredVersion contract with a settings test Structural generator mistakes surface at compile time via DSTG002-005 and partial-member matching, and emission regressions light up the fixture suite - except one: dropping the reversed bucket scan in the generated GetMinimumRequiredVersion compiles green and returns the lowest enabled feature version instead of the highest, and the method's only consumer is project-export LangVersion stamping, which default CI runs barely exercise. Pin the highest-wins contract, including the syntax-preference settings that now participate in the ladder. Assisted-by: Claude:claude-fable-5:Claude Code --- .../DecompilerSettingsTests.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 ICSharpCode.Decompiler.Tests/DecompilerSettingsTests.cs diff --git a/ICSharpCode.Decompiler.Tests/DecompilerSettingsTests.cs b/ICSharpCode.Decompiler.Tests/DecompilerSettingsTests.cs new file mode 100644 index 000000000..2645e6f93 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/DecompilerSettingsTests.cs @@ -0,0 +1,53 @@ +// Copyright (c) 2026 Siegfried Pammer +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using ICSharpCode.Decompiler.CSharp; + +using NUnit.Framework; + +namespace ICSharpCode.Decompiler.Tests +{ + [TestFixture] + class DecompilerSettingsTests + { + [Test] + public void GetMinimumRequiredVersionReturnsTheHighestEnabledFeatureVersion() + { + var settings = new DecompilerSettings(LanguageVersion.CSharp1); + Assert.That(settings.GetMinimumRequiredVersion(), Is.EqualTo(LanguageVersion.CSharp1)); + + settings.AnonymousMethods = true; + Assert.That(settings.GetMinimumRequiredVersion(), Is.EqualTo(LanguageVersion.CSharp2)); + + // Syntax-preference settings participate too: enabled, they emit syntax of their version. + settings.UseEnhancedUsing = true; + Assert.That(settings.GetMinimumRequiredVersion(), Is.EqualTo(LanguageVersion.CSharp8_0)); + + settings.SwitchOnReadOnlySpanChar = true; + Assert.That(settings.GetMinimumRequiredVersion(), Is.EqualTo(LanguageVersion.CSharp11_0)); + + // The scan must pick the highest enabled feature, not the first match bottom-up. + settings.ParamsCollections = true; + Assert.That(settings.GetMinimumRequiredVersion(), Is.EqualTo(LanguageVersion.CSharp13_0)); + + settings.AnonymousMethods = false; + settings.ParamsCollections = false; + Assert.That(settings.GetMinimumRequiredVersion(), Is.EqualTo(LanguageVersion.CSharp11_0)); + } + } +}