Skip to content

Commit 9c34d31

Browse files
Fix converter resource removal: register custom types in IC
IC only registered value-type resources (Color, String) in RegisterResourceKeys, not custom types (converters). When a converter was removed from XAML during HR, GetResourceKeys didn't include it, so Resources.Remove was never called — the converter stayed in the dictionary. Fix: IC now also registers custom types that have a public parameterless constructor, matching the UC's BuildResourceValueExpression logic. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent c26fea5 commit 9c34d31

2 files changed

Lines changed: 68 additions & 2 deletions

File tree

src/Controls/src/SourceGen/InitializeComponentCodeWriter.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,10 +189,22 @@ PrePost newblock() =>
189189
if (elem.Properties.TryGetValue(xKeyName, out var keyNode)
190190
&& keyNode is ValueNode kv && kv.Value is string keyStr)
191191
{
192-
// Only register keys for value-type resources we can encode (Color, String, etc.)
193-
// Skip custom types (converters, etc.) that IC creates via new()
192+
// Register keys for resources we can encode in UC:
193+
// Value-type resources have a single ValueNode child (Color, String, etc.)
194194
if (elem.CollectionItems.Count == 1 && elem.CollectionItems[0] is ValueNode)
195+
{
195196
resourceKeys.Add(keyStr);
197+
}
198+
// Custom types (converters, etc.) with a public parameterless constructor
199+
else if (elem.CollectionItems.Count == 0 || elem.CollectionItems.All(c => c is not ValueNode))
200+
{
201+
if (elem.XmlType.TryResolveTypeSymbol(null, compilation, xmlnsCache, typeCache, out var resTypeSymbol)
202+
&& resTypeSymbol != null
203+
&& resTypeSymbol.InstanceConstructors.Any(c => c.Parameters.Length == 0 && c.DeclaredAccessibility == Microsoft.CodeAnalysis.Accessibility.Public))
204+
{
205+
resourceKeys.Add(keyStr);
206+
}
207+
}
196208
}
197209
}
198210
}

src/Controls/tests/SourceGen.UnitTests/XamlIncrementalHotReloadPipelineTests.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1377,6 +1377,60 @@ public class StatusColorConverter : Microsoft.Maui.Controls.IValueConverter
13771377
Assert.Contains("new global::TestApp.StatusColorConverter()", uc, StringComparison.Ordinal);
13781378
}
13791379

1380+
[Fact]
1381+
public void ConverterResourceRemoved_UCEmitsRemove()
1382+
{
1383+
// Removing a converter resource should emit Resources.Remove("key") in UC.
1384+
XamlHotReloadState.Reset();
1385+
1386+
const string stubs = """
1387+
namespace TestApp
1388+
{
1389+
public class StatusColorConverter : Microsoft.Maui.Controls.IValueConverter
1390+
{
1391+
public object? Convert(object? value, System.Type targetType, object? parameter, System.Globalization.CultureInfo culture) => null;
1392+
public object? ConvertBack(object? value, System.Type targetType, object? parameter, System.Globalization.CultureInfo culture) => null;
1393+
}
1394+
}
1395+
""";
1396+
1397+
const string xamlV1 = """
1398+
<?xml version="1.0" encoding="utf-8" ?>
1399+
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
1400+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
1401+
xmlns:local="clr-namespace:TestApp"
1402+
x:Class="TestApp.MainPage">
1403+
<ContentPage.Resources>
1404+
<Color x:Key="AccentColor">DarkBlue</Color>
1405+
<local:StatusColorConverter x:Key="StatusConverter" />
1406+
</ContentPage.Resources>
1407+
<Label Text="Hello" />
1408+
</ContentPage>
1409+
""";
1410+
const string xamlV2 = """
1411+
<?xml version="1.0" encoding="utf-8" ?>
1412+
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
1413+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
1414+
xmlns:local="clr-namespace:TestApp"
1415+
x:Class="TestApp.MainPage">
1416+
<ContentPage.Resources>
1417+
<Color x:Key="AccentColor">DarkBlue</Color>
1418+
</ContentPage.Resources>
1419+
<Label Text="Hello" />
1420+
</ContentPage>
1421+
""";
1422+
1423+
var (_, run2) = TwoRunsWithSource(xamlV1, xamlV2, stubs);
1424+
var uc = FindUCSource(run2, "uc.xsg");
1425+
1426+
Assert.NotNull(uc);
1427+
Assert.Contains("__version = 1", uc, StringComparison.Ordinal);
1428+
// UC must remove the converter from the dictionary
1429+
Assert.Contains("Resources.Remove", uc, StringComparison.Ordinal);
1430+
// The converter key should no longer be registered
1431+
Assert.DoesNotContain("\"StatusConverter\"", uc.Substring(uc.IndexOf("RegisterResourceKeys")), StringComparison.Ordinal);
1432+
}
1433+
13801434
[Fact]
13811435
public void ConverterSwap_UCCompilesCleanly()
13821436
{

0 commit comments

Comments
 (0)