diff --git a/src/Controls/src/SourceGen/NodeSGExtensions.cs b/src/Controls/src/SourceGen/NodeSGExtensions.cs
index 034c23953fa8..1f51c10eea38 100644
--- a/src/Controls/src/SourceGen/NodeSGExtensions.cs
+++ b/src/Controls/src/SourceGen/NodeSGExtensions.cs
@@ -548,6 +548,15 @@ public static bool TryProvideValue(this ElementNode node, IndentedTextWriter wri
if (GetKnownValueProviders(context).TryGetValue(variable.Type, out var valueProvider)
&& valueProvider.TryProvideValue(node, writer, context, getNodeValue, out returnType0, out value))
{
+ // Check for "skip this node" sentinel: returnType is null and value is empty
+ // This happens when a Setter has no value (e.g., OnPlatform with no matching platform)
+ if (returnType0 is null && string.IsNullOrEmpty(value))
+ {
+ // Remove from Variables so it won't be added to any collection
+ context.Variables.Remove(node);
+ return true;
+ }
+
var variableName = NamingHelpers.CreateUniqueVariableName(context, returnType0 ?? context.Compilation.ObjectType);
context.Writer.WriteLine($"var {variableName} = {value};");
context.Variables[node] = new LocalVariable(returnType0 ?? context.Compilation.ObjectType, variableName);
diff --git a/src/Controls/src/SourceGen/SetPropertyHelpers.cs b/src/Controls/src/SourceGen/SetPropertyHelpers.cs
index 0805bd2d48c0..b7d7fa91455e 100644
--- a/src/Controls/src/SourceGen/SetPropertyHelpers.cs
+++ b/src/Controls/src/SourceGen/SetPropertyHelpers.cs
@@ -71,6 +71,10 @@ public static void SetPropertyValue(IndentedTextWriter writer, ILocalValue paren
return;
}
+ // If the node was removed from Variables (e.g., Setter with no value due to OnPlatform), skip silently
+ if (valueNode is ElementNode en && !context.Variables.ContainsKey(en))
+ return;
+
var location = LocationCreate(context.ProjectItem.RelativePath!, (IXmlLineInfo)valueNode, localName);
context.ReportDiagnostic(Diagnostic.Create(Descriptors.MemberResolution, location, localName));
}
diff --git a/src/Controls/src/SourceGen/SetterValueProvider.cs b/src/Controls/src/SourceGen/SetterValueProvider.cs
index 7171e5e27ded..130a4b2adb33 100644
--- a/src/Controls/src/SourceGen/SetterValueProvider.cs
+++ b/src/Controls/src/SourceGen/SetterValueProvider.cs
@@ -16,6 +16,11 @@ public bool CanProvideValue(ElementNode node, SourceGenContext context)
// Get the value node (shared logic with TryProvideValue)
var valueNode = GetValueNode(node);
+ // Must have a value node to provide a value
+ // This can be null when OnPlatform removes the Value property (no matching platform, no Default)
+ if (valueNode == null)
+ return false;
+
// Value must be a simple ValueNode (not a MarkupNode or ElementNode)
if (valueNode is MarkupNode or ElementNode)
return false;
@@ -38,8 +43,11 @@ public bool TryProvideValue(ElementNode node, IndentedTextWriter writer, SourceG
var valueNode = GetValueNode(node);
if (valueNode == null)
{
+ // The value was removed (e.g., OnPlatform with no matching platform and no Default)
+ // Signal to skip this Setter entirely by returning true with null returnType and empty value
+ returnType = null;
value = string.Empty;
- return false;
+ return true;
}
var bpNode = (ValueNode)node.Properties[new XmlName("", "Property")];
diff --git a/src/Controls/src/SourceGen/Visitors/SetPropertiesVisitor.cs b/src/Controls/src/SourceGen/Visitors/SetPropertiesVisitor.cs
index a933c0cf13f7..29e57c245292 100644
--- a/src/Controls/src/SourceGen/Visitors/SetPropertiesVisitor.cs
+++ b/src/Controls/src/SourceGen/Visitors/SetPropertiesVisitor.cs
@@ -148,7 +148,9 @@ public void Visit(ElementNode node, INode parentNode)
}
else if (parentVar.Type.CanAdd(context))
{
- Writer.WriteLine($"{parentVar.ValueAccessor}.Add({Context.Variables[node].ValueAccessor});");
+ // Skip if the node was removed from Variables (e.g., Setter with no value due to OnPlatform)
+ if (Context.Variables.TryGetValue(node, out var nodeVar))
+ Writer.WriteLine($"{parentVar.ValueAccessor}.Add({nodeVar.ValueAccessor});");
}
else
{
@@ -196,8 +198,9 @@ public void Visit(ElementNode node, INode parentNode)
if (propertyType.CanAdd(context))
{
- Writer.WriteLine($"{variable.ValueAccessor}.Add({Context.Variables[node].ValueAccessor});");
-
+ // Skip if the node was removed from Variables (e.g., Setter with no value due to OnPlatform)
+ if (Context.Variables.TryGetValue(node, out var nodeVar))
+ Writer.WriteLine($"{variable.ValueAccessor}.Add({nodeVar.ValueAccessor});");
}
else
//report diagnostic: not a collection
diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Maui33676.xaml b/src/Controls/tests/Xaml.UnitTests/Issues/Maui33676.xaml
new file mode 100644
index 000000000000..e9c7b4c24c3e
--- /dev/null
+++ b/src/Controls/tests/Xaml.UnitTests/Issues/Maui33676.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Maui33676.xaml.cs b/src/Controls/tests/Xaml.UnitTests/Issues/Maui33676.xaml.cs
new file mode 100644
index 000000000000..08c227f76b73
--- /dev/null
+++ b/src/Controls/tests/Xaml.UnitTests/Issues/Maui33676.xaml.cs
@@ -0,0 +1,96 @@
+using System;
+using Microsoft.Maui.Controls.Core.UnitTests;
+using Microsoft.Maui.Devices;
+using Xunit;
+
+using static Microsoft.Maui.Controls.Xaml.UnitTests.MockSourceGenerator;
+
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public partial class Maui33676 : ContentPage
+{
+ public Maui33676() => InitializeComponent();
+
+ [Collection("Issue")]
+ public class Tests : IDisposable
+ {
+ MockDeviceInfo mockDeviceInfo;
+
+ public Tests() => DeviceInfo.SetCurrent(mockDeviceInfo = new MockDeviceInfo());
+
+ public void Dispose() => DeviceInfo.SetCurrent(null);
+
+ [Theory]
+ [XamlInflatorData]
+ // BUG: When a Setter uses OnPlatform without a Default value and the target platform
+ // doesn't match any of the specified platforms, SourceGen generates invalid code:
+ // ((IValueProvider)).ProvideValue(...) - trying to call a method on a type instead of an instance
+ //
+ // Expected behavior: The Setter should be skipped entirely or use a default value
+ internal void SetterWithOnPlatformWithoutDefaultShouldNotGenerateInvalidCode(XamlInflator inflator)
+ {
+ // Test compiling for Android when OnPlatform only specifies iOS
+ if (inflator == XamlInflator.SourceGen)
+ {
+ var result = CreateMauiCompilation()
+ .WithAdditionalSource(
+"""
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public partial class Maui33676 : ContentPage
+{
+ public Maui33676() => InitializeComponent();
+}
+""")
+ .RunMauiSourceGenerator(typeof(Maui33676), targetFramework: "net10.0-android");
+
+ var generated = result.GeneratedInitializeComponent();
+
+ // BUG: Generated code contains invalid C# like:
+ // ((global::Microsoft.Maui.Controls.Xaml.IValueProvider)).ProvideValue(...)
+ // This causes: CS0119 'IValueProvider' is a type, which is not valid in the given context
+ // This should NOT happen - when OnPlatform has no matching value, the Setter should be omitted
+ Assert.DoesNotContain("((global::Microsoft.Maui.Controls.Xaml.IValueProvider)).ProvideValue", generated, StringComparison.Ordinal);
+
+ Assert.Empty(result.Diagnostics);
+ }
+ else
+ {
+ mockDeviceInfo.Platform = DevicePlatform.Android;
+ var page = new Maui33676(inflator);
+ Assert.NotNull(page);
+ }
+ }
+
+ [Theory]
+ [XamlInflatorData]
+ // When the platform DOES match, the Setter should work correctly
+ internal void SetterWithOnPlatformMatchingPlatform(XamlInflator inflator)
+ {
+ if (inflator == XamlInflator.SourceGen)
+ {
+ var result = CreateMauiCompilation()
+ .WithAdditionalSource(
+"""
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public partial class Maui33676 : ContentPage
+{
+ public Maui33676() => InitializeComponent();
+}
+""")
+ .RunMauiSourceGenerator(typeof(Maui33676), targetFramework: "net10.0-ios");
+
+ Assert.Empty(result.Diagnostics);
+ var generated = result.GeneratedInitializeComponent();
+ Assert.Contains("0, 4, 0, 0", generated, StringComparison.Ordinal);
+ }
+ else
+ {
+ mockDeviceInfo.Platform = DevicePlatform.iOS;
+ var page = new Maui33676(inflator);
+ Assert.NotNull(page);
+ }
+ }
+ }
+}
diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Maui33676_VisualState.xaml b/src/Controls/tests/Xaml.UnitTests/Issues/Maui33676_VisualState.xaml
new file mode 100644
index 000000000000..73365c76939c
--- /dev/null
+++ b/src/Controls/tests/Xaml.UnitTests/Issues/Maui33676_VisualState.xaml
@@ -0,0 +1,25 @@
+
+
+
+ #0D0D0D
+ #E0E0E0
+
+
+
+
diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Maui33676_VisualState.xaml.cs b/src/Controls/tests/Xaml.UnitTests/Issues/Maui33676_VisualState.xaml.cs
new file mode 100644
index 000000000000..9faf5b0017d6
--- /dev/null
+++ b/src/Controls/tests/Xaml.UnitTests/Issues/Maui33676_VisualState.xaml.cs
@@ -0,0 +1,61 @@
+using System;
+using Microsoft.Maui.Controls.Core.UnitTests;
+using Microsoft.Maui.Devices;
+using Xunit;
+
+using static Microsoft.Maui.Controls.Xaml.UnitTests.MockSourceGenerator;
+
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public partial class Maui33676_VisualState : ContentPage
+{
+ public Maui33676_VisualState() => InitializeComponent();
+
+ [Collection("Issue")]
+ public class Tests : IDisposable
+ {
+ MockDeviceInfo mockDeviceInfo;
+
+ public Tests() => DeviceInfo.SetCurrent(mockDeviceInfo = new MockDeviceInfo());
+
+ public void Dispose() => DeviceInfo.SetCurrent(null);
+
+ [Theory]
+ [XamlInflatorData]
+ // When a Setter inside VisualState uses OnPlatform without a Default value and the target platform
+ // doesn't match, SourceGen should handle it gracefully
+ internal void SetterInVisualStateWithOnPlatformWithoutDefaultShouldNotGenerateInvalidCode(XamlInflator inflator)
+ {
+ // Test compiling for MacCatalyst when OnPlatform only specifies Android
+ if (inflator == XamlInflator.SourceGen)
+ {
+ var result = CreateMauiCompilation()
+ .WithAdditionalSource(
+"""
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public partial class Maui33676_VisualState : ContentPage
+{
+ public Maui33676_VisualState() => InitializeComponent();
+}
+""")
+ .RunMauiSourceGenerator(typeof(Maui33676_VisualState), targetFramework: "net10.0-maccatalyst");
+
+ var generated = result.GeneratedInitializeComponent();
+
+ // Check that we're NOT generating code that tries to add object to IList
+ // This would happen if IValueProvider.ProvideValue() is called on a Setter with no value
+ Assert.DoesNotContain("((global::Microsoft.Maui.Controls.Xaml.IValueProvider)).ProvideValue", generated, StringComparison.Ordinal);
+
+ // Should not have any compilation errors
+ Assert.Empty(result.Diagnostics);
+ }
+ else
+ {
+ mockDeviceInfo.Platform = DevicePlatform.macOS;
+ var page = new Maui33676_VisualState(inflator);
+ Assert.NotNull(page);
+ }
+ }
+ }
+}