Skip to content

Allow overriding an assembly's target framework for reference resolution#3816

Merged
siegfriedpammer merged 1 commit into
masterfrom
tfm-override
Jul 1, 2026
Merged

Allow overriding an assembly's target framework for reference resolution#3816
siegfriedpammer merged 1 commit into
masterfrom
tfm-override

Conversation

@siegfriedpammer

@siegfriedpammer siegfriedpammer commented Jun 24, 2026

Copy link
Copy Markdown
Member

What

Lets the user override the target framework ILSpy resolves an assembly's references against, instead of the one detected from its TargetFrameworkAttribute. This helps when the attribute is missing, wrong, or when the user wants to force resolution against a specific framework, so references do not resolve against the wrong runtime pack or framework directory.

Resurrects a 2020 prototype (branch tfmoverride), re-implemented against the current ICSharpCode.ILSpyX / Avalonia code whose surrounding structures no longer matched.

How

  • Core (ICSharpCode.ILSpyX): LoadedAssembly.TargetFrameworkIdOverride short-circuits GetTargetFrameworkIdAsync() ahead of the cached detected value, so LoadedAssembly-based resolution paths read the effective TFM. The detected value remains available separately for UI/reporting. The override is persisted as a backward-compatible TargetFramework attribute on the <Assembly> element and carried across ReloadAssembly.
  • UI (ILSpy): a "Set Target Framework..." context-menu entry on assembly nodes opens a dialog to edit the override, then reloads so references re-resolve and restores the tree selection. The dialog pairs a free-form text box with a list of common monikers; input is validated and converted from the short TFM users know (net48) to the long FrameworkName form the resolver consumes (.NETFramework,Version=v4.8) via NuGet. It returns null for cancel, an empty string to reset to auto-detection, and a non-empty string to set an override.
  • Visual distinction & reset: an assembly carrying a manual override bolds its effective TFM in the tree label, so overridden assemblies stand out at a glance. A separate "Reset Target Framework" entry appears only while an override is in effect, clearing it back to auto-detection without reopening the dialog.
  • References listing: the References folder prints both Detected TargetFramework-Id and Effective TargetFramework-Id, so overrides are visible without relabeling detected metadata.
  • NuGet.Frameworks is promoted to an explicit reference (already resolved transitively at 7.6.0 via NuGet.Protocol); lock files regenerated.

Screenshots

Set the override from the assembly context menu (no override yet, so only "Set Target Framework..." is offered):

Assembly context menu with the Set Target Framework entry, no override active

Enter the framework as the short moniker you know (net48); the presets list covers the common ones:

Set Target Framework dialog with net48 entered and highlighted in the presets list

After the reload, the effective TFM is bolded in the tree label and the References folder reports Detected vs Effective side by side:

Overridden assembly with the effective TFM bolded in the tree label and the References folder showing Detected versus Effective

While an override is active, a "Reset Target Framework" entry clears it back to auto-detection:

Assembly context menu with the Reset Target Framework entry while an override is active

Scope

Only reference resolution is overridden. The direct DetectTargetFrameworkId callers in the decompiler core (project export, language version) keep reading the real attribute.

Tests / verification

  • Coverage includes override precedence, XML round-trip/backward compatibility, reload carry-over, short-to-long TFM conversion, invalid moniker rejection, and References listing headers.
  • Focused local test run after the latest updates:
    • OPENSSL_ENABLE_SHA1_SIGNATURES=1 dotnet test --project ILSpy.Tests/ILSpy.Tests.csproj --filter "FullyQualifiedName~DecompilerViewTests" --report-trx --no-restore -v:minimal
    • Result: Test run summary: Passed! failed: 0
  • Local build after the latest updates:
    • OPENSSL_ENABLE_SHA1_SIGNATURES=1 pwsh ./build.ps1 -Configuration Debug --no-restore /m:1
    • Result: Build succeeded. 0 Warning(s) 0 Error(s)
  • Verified in the running app: the menu entry appears, the dialog opens prefilled with the detected TFM in short form, net48 -> .NETFramework,Version=v4.8 drives GetTargetFrameworkIdAsync after reload, invalid input shows an inline error and keeps the dialog open, and blank clears the override.

@siegfriedpammer siegfriedpammer force-pushed the tfm-override branch 3 times, most recently from 5380600 to a4f1a3f Compare June 25, 2026 20:59
Comment thread ILSpy/TreeNodes/ReferenceFolderTreeNode.cs Outdated
@christophwille

christophwille commented Jun 26, 2026

Copy link
Copy Markdown
Member

A visual distinction between auto-detected framework and manually locked-in ones would be nice.

Also, a context menu item to "Reset to auto" (only visible when manually locked-in) instead of having to go through the dialog when manually locked-in would be nice.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for overriding an assembly’s effective Target Framework Moniker (TFM) used for reference resolution, including persistence in assembly-list XML and an ILSpy UI flow to edit/reset the override.

Changes:

  • Core: introduce LoadedAssembly.TargetFrameworkIdOverride, plus separation of detected vs effective TFM (GetDetectedTargetFrameworkIdAsync() vs GetTargetFrameworkIdAsync()).
  • UI: add “Set Target Framework...” context menu entry and dialog; validate/convert user input via NuGet.Frameworks.
  • Tests/deps: add unit tests for conversion + override persistence/reload; promote NuGet.Frameworks to an explicit dependency and update lock files.

Reviewed changes

Copilot reviewed 20 out of 21 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
TestPlugin/packages.lock.json Lockfile updates to reflect NuGet.Frameworks dependency changes.
ILSpy/Views/TargetFrameworkConverter.cs New helper to parse/validate TFMs and convert to/from FrameworkName form.
ILSpy/Views/SetTargetFrameworkDialog.axaml.cs New dialog logic (presets, validation, OK/cancel behavior).
ILSpy/Views/SetTargetFrameworkDialog.axaml New dialog UI layout for setting/resetting TFM override.
ILSpy/TreeNodes/ReferenceFolderTreeNode.cs References listing now prints detected vs effective TFM.
ILSpy/Properties/Resources.resx Adds strings for dialog label/title and invalid-input message.
ILSpy/Properties/Resources.Designer.cs Generated resource accessors for new strings.
ILSpy/packages.lock.json Adds NuGet.Frameworks as direct dependency; updates lockfile graph.
ILSpy/ILSpy.csproj Adds explicit PackageReference to NuGet.Frameworks.
ILSpy/Commands/SetTargetFrameworkContextMenuEntry.cs New context menu entry to edit override and reload assembly while restoring selection.
ILSpy.Tests/Views/TargetFrameworkConverterTests.cs Unit tests for short/long TFM conversion and invalid input rejection.
ILSpy.Tests/packages.lock.json Lockfile updates for NuGet.Frameworks graph changes.
ILSpy.Tests/Editor/DecompilerViewTests.cs Updates expected References-folder output to include effective TFM header.
ILSpy.Tests/AssemblyList/TargetFrameworkOverrideTests.cs Tests override precedence, XML round-trip compatibility, and reload carry-over.
ILSpy.Tests.Windows/packages.lock.json Lockfile updates for NuGet.Frameworks graph changes.
ILSpy.ReadyToRun/packages.lock.json Lockfile updates for NuGet.Frameworks graph changes.
ICSharpCode.ILSpyX/LoadedAssembly.cs Implements effective vs detected TFM APIs and adds TargetFrameworkIdOverride.
ICSharpCode.ILSpyX/AssemblyList.cs Persists override in assembly-list XML and carries it across reload.
ICSharpCode.ILSpyCmd/packages.lock.json Lockfile updates for NuGet.Frameworks graph changes.
ICSharpCode.Decompiler.Tests/packages.lock.json Lockfile updates for NuGet.Frameworks graph changes.
Directory.Packages.props Adds centrally-managed NuGet.Frameworks version.
Files not reviewed (1)
  • ILSpy/Properties/Resources.Designer.cs: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread ICSharpCode.ILSpyX/LoadedAssembly.cs
Comment thread ICSharpCode.ILSpyX/LoadedAssembly.cs
Comment thread ICSharpCode.ILSpyX/LoadedAssembly.cs
Comment thread ICSharpCode.ILSpyX/AssemblyList.cs
Comment thread ICSharpCode.ILSpyX/AssemblyList.cs

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 22 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • ILSpy/Properties/Resources.Designer.cs: Generated file

Comment thread ILSpy/Commands/SetTargetFrameworkContextMenuEntry.cs Outdated
Comment thread ILSpy/Commands/SetTargetFrameworkContextMenuEntry.cs Outdated
ILSpy resolves an assembly's references against the target framework it
detects from the TargetFrameworkAttribute. When that attribute is missing,
wrong, or the user wants to force a different framework, there was no way to
hint the correct one, so references could resolve against the wrong runtime
pack or framework directory.

A LoadedAssembly can now carry a TargetFrameworkIdOverride that short-circuits
detection (it is the single value every LoadedAssembly-based resolution path
reads), is persisted in the assembly-list XML, and is carried across a reload
so a runtime change re-resolves against the new framework. The "Set Target
Framework" context-menu entry edits it through a dialog with a free-form text
box and an always-visible list of common monikers to pick from (the app forces
overlay popups, so a dropdown would be clamped inside the small dialog); input
is validated and converted from the short TFM users know (net48) to the long
FrameworkName form the resolver consumes (.NETFramework,Version=v4.8) via
NuGet. The direct DetectTargetFrameworkId callers in the decompiler core
(project export, language version) intentionally keep reading the real
attribute; only reference resolution is overridden.

Resurrects a 2020 prototype (branch tfmoverride) re-implemented against the
current ILSpyX/Avalonia code, whose surrounding structures no longer matched.

Assisted-by: Claude:claude-opus-4-8:Claude Code
@siegfriedpammer

Copy link
Copy Markdown
Member Author

All review feedback is addressed as of 8170422: blank/whitespace overrides normalize to null in the setter (detection is never suppressed), the TFM doc-comments use the comma form, the Debug-menu Order collision is gone (Set/Reset = 420/421), overridden assemblies bold their TFM fragment in the tree, and the References listing folds the awaits into one async call. Thanks!

Written by Claude (claude-opus-4-8) via Claude Code, on Siegfried's behalf.

@siegfriedpammer siegfriedpammer merged commit 17b3539 into master Jul 1, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants