From 590793fd215681d72d3b82ca49380716640b421b Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 9 Aug 2026 12:21:28 +0200 Subject: [PATCH] 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. ///