mirror of https://github.com/icsharpcode/ILSpy.git
Browse Source
LoadedAssembly caches one type system per TypeSystemOptions value, but the consumers disagreed on which one to use: tree nodes and the metadata navigator resolved through GetTypeSystemOrNull (a separate default-options instance) and the search built its request with default settings. Each module could therefore materialise several type systems, and none of the display surfaces respected settings that change entity shapes (nullability, tuples, extension methods, ...). WPF routed all of these through GetTypeSystemWithCurrentOptionsOrNull. Reintroduce that helper on top of CreateEffectiveDecompilerSettings and use it in TypeTreeNode, AssemblyReferenceTreeNode, and the metadata navigator; the search request now derives its settings the same way, so all of them hit the same cached instance. NamespaceTreeNode.Decompile enumerates through the type system matching the decompilation options it was handed. Assisted-by: Claude:claude-fable-5:Claude Codepull/3404/head
8 changed files with 155 additions and 14 deletions
@ -0,0 +1,91 @@ |
|||||||
|
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
|
||||||
|
//
|
||||||
|
// 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.ObjectModel; |
||||||
|
using System.Threading.Tasks; |
||||||
|
|
||||||
|
using Avalonia.Headless.NUnit; |
||||||
|
|
||||||
|
using AwesomeAssertions; |
||||||
|
|
||||||
|
using ICSharpCode.ILSpy.AppEnv; |
||||||
|
using ICSharpCode.ILSpy.Languages; |
||||||
|
using ICSharpCode.ILSpy.Search; |
||||||
|
using ICSharpCode.ILSpy.TreeNodes; |
||||||
|
using ICSharpCode.ILSpyX; |
||||||
|
using ICSharpCode.ILSpyX.Extensions; |
||||||
|
using ICSharpCode.ILSpyX.Search; |
||||||
|
|
||||||
|
using NUnit.Framework; |
||||||
|
|
||||||
|
namespace ICSharpCode.ILSpy.Tests.TreeNodes; |
||||||
|
|
||||||
|
// LoadedAssembly caches ONE type system per TypeSystemOptions value
|
||||||
|
// (GetTypeSystemOrNull(options)). The tree, the search, and anything else that resolves
|
||||||
|
// entities for display must all derive their options from the user's current decompiler
|
||||||
|
// settings: that keeps them consistent with what decompilation elides AND makes them share
|
||||||
|
// the single cached instance instead of materialising a second type system per module.
|
||||||
|
[TestFixture] |
||||||
|
public class TypeSystemSharingTests |
||||||
|
{ |
||||||
|
[AvaloniaTest] |
||||||
|
public async Task Tree_nodes_resolve_through_the_settings_derived_type_system() |
||||||
|
{ |
||||||
|
var (_, vm) = await TestHarness.BootAsync(); |
||||||
|
var settingsService = AppComposition.Current.GetExport<SettingsService>(); |
||||||
|
var fixture = await vm.OpenFixtureAsync(); |
||||||
|
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>( |
||||||
|
fixture.ShortName, fixture.ShortName, $"{fixture.ShortName}.{FixtureAssembly.TypeName}"); |
||||||
|
Assert.That(typeNode, Is.Not.Null); |
||||||
|
|
||||||
|
var module = fixture.GetMetadataFileOrNull()!; |
||||||
|
var expected = module.GetTypeSystemWithDecompilerSettingsOrNull( |
||||||
|
settingsService.CreateEffectiveDecompilerSettings()); |
||||||
|
|
||||||
|
typeNode!.Member!.Compilation.Should().BeSameAs(expected, |
||||||
|
"the tree must resolve entities through the cached type system derived from the " + |
||||||
|
"current decompiler settings, not a separate default-options one"); |
||||||
|
} |
||||||
|
|
||||||
|
[AvaloniaTest] |
||||||
|
public async Task Search_reuses_the_same_cached_type_system_as_the_tree() |
||||||
|
{ |
||||||
|
var (_, vm) = await TestHarness.BootAsync(); |
||||||
|
var settingsService = AppComposition.Current.GetExport<SettingsService>(); |
||||||
|
// A non-default option that changes the derived TypeSystemOptions: a search that
|
||||||
|
// ignores the live settings materialises a second, default-options type system.
|
||||||
|
settingsService.DecompilerSettings.NullableReferenceTypes = false; |
||||||
|
var fixture = await vm.OpenFixtureAsync(); |
||||||
|
var module = fixture.GetMetadataFileOrNull()!; |
||||||
|
var language = AppComposition.Current.GetExport<LanguageService>().CurrentLanguage; |
||||||
|
|
||||||
|
var search = new RunningSearch( |
||||||
|
new[] { fixture }, FixtureAssembly.TypeName, SearchMode.TypeAndMember, language, |
||||||
|
ApiVisibility.PublicAndInternal, new AvaloniaSearchResultFactory(language), |
||||||
|
new ObservableCollection<SearchResult>(), SearchResult.ComparerByName); |
||||||
|
var request = search.BuildRequest(); |
||||||
|
|
||||||
|
var fromSearch = module.GetTypeSystemWithDecompilerSettingsOrNull(request.DecompilerSettings); |
||||||
|
var effective = module.GetTypeSystemWithDecompilerSettingsOrNull( |
||||||
|
settingsService.CreateEffectiveDecompilerSettings()); |
||||||
|
|
||||||
|
fromSearch.Should().BeSameAs(effective, |
||||||
|
"the search must derive its type system from the current decompiler settings so it " + |
||||||
|
"shares the per-module cache with the tree and respects the user's options"); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,44 @@ |
|||||||
|
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
|
||||||
|
//
|
||||||
|
// 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.Metadata; |
||||||
|
using ICSharpCode.Decompiler.TypeSystem; |
||||||
|
using ICSharpCode.ILSpyX; |
||||||
|
|
||||||
|
namespace ICSharpCode.ILSpy |
||||||
|
{ |
||||||
|
public static class ExtensionMethods |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Returns the cached type system for <paramref name="file"/> derived from the user's
|
||||||
|
/// current decompiler settings (see
|
||||||
|
/// <see cref="SettingsService.CreateEffectiveDecompilerSettings"/>). Everything that
|
||||||
|
/// resolves entities for display — tree nodes, search, metadata views — should go
|
||||||
|
/// through this so it agrees with what decompilation produces and shares the
|
||||||
|
/// per-module type-system cache instead of materialising another instance. Falls back
|
||||||
|
/// to default settings when composition is unavailable (design-time, minimal test
|
||||||
|
/// hosts).
|
||||||
|
/// </summary>
|
||||||
|
public static ICompilation? GetTypeSystemWithCurrentOptionsOrNull(this MetadataFile file) |
||||||
|
{ |
||||||
|
var settings = AppEnv.AppComposition.TryGetExport<SettingsService>()?.CreateEffectiveDecompilerSettings() |
||||||
|
?? new Decompiler.DecompilerSettings(); |
||||||
|
return file.GetTypeSystemWithDecompilerSettingsOrNull(settings); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue