Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 1 addition & 53 deletions Terminal.Gui/Configuration/ConfigurationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ internal static void Initialize ()
// Some getters (notably ThemeManager.Themes while uninitialized) synthesize composite
// objects from other configuration properties, so a single populate-and-clone pass can
// observe half-initialized cache entries and persist null/default values into the clone.
hardCodedProperty.Value.PropertyValue = CloneHardCodedPropertyValue (hardCodedProperty.Value.PropertyValue);
hardCodedProperty.Value.PropertyValue = DeepCloner.DeepClone (hardCodedProperty.Value.PropertyValue);
hardCodedProperty.Value.Immutable = true;
}
}
Expand All @@ -222,58 +222,6 @@ internal static void Initialize ()
ThemeManager.Themes? [ThemeManager.Theme].Apply ();
}

// AOT trimming removes the reflective Dictionary<,> constructor that DeepCloner relies on.
// Each non-trivially-cloneable config property type needs a typed clone path here to stay AOT-safe.
private static object? CloneHardCodedPropertyValue (object? propertyValue)
{
if (propertyValue is Dictionary<Command, PlatformKeyBinding> keyBindings)
{
return CloneKeyBindings (keyBindings);
}

return DeepCloner.DeepClone (propertyValue);
}

private static Dictionary<Command, PlatformKeyBinding> CloneKeyBindings (Dictionary<Command, PlatformKeyBinding> source)
{
Dictionary<Command, PlatformKeyBinding> clone = new (source.Comparer);

foreach (KeyValuePair<Command, PlatformKeyBinding> kvp in source)
{
clone [kvp.Key] = ClonePlatformKeyBinding (kvp.Value);
}

return clone;
}

private static PlatformKeyBinding ClonePlatformKeyBinding (PlatformKeyBinding binding)
{
return binding with
{
All = CloneKeyArray (binding.All),
Windows = CloneKeyArray (binding.Windows),
Linux = CloneKeyArray (binding.Linux),
Macos = CloneKeyArray (binding.Macos)
};
}

private static Key []? CloneKeyArray (Key []? keys)
{
if (keys is null)
{
return null;
}

Key [] clonedKeys = new Key [keys.Length];

for (int i = 0; i < keys.Length; i++)
{
clonedKeys [i] = new (keys [i]);
}

return clonedKeys;
}

#endregion Initialization

#region Enable/Disable
Expand Down
27 changes: 27 additions & 0 deletions Terminal.Gui/Configuration/DeepCloner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@ private static object CloneDictionary (object source, ConcurrentDictionary<objec
[UnconditionalSuppressMessage ("Trimming", "IL2067", Justification = "Dictionary cloning only instantiates supported dictionary runtime types and falls back safely when comparer constructors are unavailable.")]
private static IDictionary CreateDictionaryInstance (Type dictType, object? comparer)
{
// Typed paths for dictionary types that require custom comparers.
if (dictType == typeof (ConcurrentDictionary<string, ThemeScope>))
{
if (comparer is IEqualityComparer<string> stringComparer)
Expand All @@ -389,6 +390,32 @@ private static IDictionary CreateDictionaryInstance (Type dictType, object? comp
return new Dictionary<string, Scheme?> ();
}

// AOT-safe: use the source-generated JSON serializer to create empty dictionary instances.
// This avoids Activator.CreateInstance, whose target constructors are trimmed by the AOT linker
// for closed generic dictionary types not otherwise statically reachable.
// Only used when no comparer is needed — Deserialize("{}") always creates a default-comparer instance.
if (comparer is null)
{
try
{
JsonTypeInfo? jsonTypeInfo = ConfigurationManager.SerializerContext.GetTypeInfo (dictType);

if (jsonTypeInfo is not null)
{
IDictionary? result = JsonSerializer.Deserialize ("{}", jsonTypeInfo) as IDictionary;

if (result is not null)
{
return result;
}
}
}
catch (InvalidOperationException)
{
// JSON serializer context may not be initialized — fall through to reflective construction.
}
}

try
{
// Try to create the dictionary with the comparer
Expand Down
Loading