diff --git a/src/Controls/src/SourceGen/KnownMarkups.cs b/src/Controls/src/SourceGen/KnownMarkups.cs
index d6f2c250eff6..fc4dee30ed1b 100644
--- a/src/Controls/src/SourceGen/KnownMarkups.cs
+++ b/src/Controls/src/SourceGen/KnownMarkups.cs
@@ -308,7 +308,16 @@ private static bool ProvideValueForBindingExtension(ElementNode markupNode, Inde
{
returnType = context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.BindingBase")!;
ITypeSymbol? dataTypeSymbol = null;
- if ( context.Variables.TryGetValue(markupNode, out ILocalValue extVariable)
+
+ // Check if the binding has a Source property with a RelativeSource.
+ // In this case, we should NOT compile the binding using x:DataType because
+ // the source type will be determined at runtime by the RelativeSource, not x:DataType.
+ bool hasRelativeSource = HasRelativeSourceBinding(markupNode);
+
+ context.Variables.TryGetValue(markupNode, out ILocalValue? extVariable);
+
+ if ( !hasRelativeSource
+ && extVariable is not null
&& TryGetXDataType(markupNode, context, out dataTypeSymbol)
&& dataTypeSymbol is not null)
{
@@ -588,6 +597,29 @@ static bool IsBindingContextBinding(ElementNode node)
&& propertyName.NamespaceURI == ""
&& propertyName.LocalName == "BindingContext";
}
+
+ // Checks if the binding has a Source property that is a RelativeSource extension.
+ // When a binding uses RelativeSource, the source type is determined at runtime,
+ // so we should NOT compile the binding using x:DataType.
+ static bool HasRelativeSourceBinding(ElementNode bindingNode)
+ {
+ // Check if Source property exists
+ if (!bindingNode.Properties.TryGetValue(new XmlName("", "Source"), out INode? sourceNode)
+ && !bindingNode.Properties.TryGetValue(new XmlName(null, "Source"), out sourceNode))
+ {
+ return false;
+ }
+
+ // Check if the Source is a RelativeSourceExtension
+ if (sourceNode is ElementNode sourceElementNode)
+ {
+ // Check if the element is a RelativeSourceExtension
+ return sourceElementNode.XmlType.Name == "RelativeSourceExtension"
+ || sourceElementNode.XmlType.Name == "RelativeSource";
+ }
+
+ return false;
+ }
}
internal static bool ProvideValueForDataTemplateExtension(ElementNode markupNode, IndentedTextWriter writer, SourceGenContext context, NodeSGExtensions.GetNodeValueDelegate? getNodeValue, out ITypeSymbol? returnType, out string value)
diff --git a/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/RelativeSourceBindings.cs b/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/RelativeSourceBindings.cs
index bedde10d5c1f..09e0593d99e8 100644
--- a/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/RelativeSourceBindings.cs
+++ b/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/RelativeSourceBindings.cs
@@ -281,4 +281,125 @@ public class TestViewModel
Assert.Contains("RelativeBindingSource", generated, StringComparison.Ordinal);
Assert.Contains("FindAncestorBindingContext", generated, StringComparison.Ordinal);
}
+
+ [Fact]
+ public void SelfPathWithFindAncestorGeneratesValidCode()
+ {
+ // Test for regression: RelativeSource AncestorType with Path=. should return the ancestor itself
+ var xaml =
+"""
+
+
+
+
+
+
+
+
+""";
+
+ var code =
+"""
+#nullable enable
+using System.Windows.Input;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui.Controls.Xaml;
+
+namespace Test;
+
+[XamlProcessing(XamlInflator.SourceGen)]
+public partial class TestPage : ContentPage
+{
+ public TestPage()
+ {
+ InitializeComponent();
+ BindingContext = this;
+ }
+
+ public ICommand Navigate => new Command((param) => { });
+}
+""";
+
+ var (result, generated) = RunGenerator(xaml, code);
+
+ // Verify no diagnostics
+ Assert.False(result.Diagnostics.Any(d => d.Severity == Microsoft.CodeAnalysis.DiagnosticSeverity.Error),
+ $"Generator produced errors: {string.Join(", ", result.Diagnostics.Where(d => d.Severity == Microsoft.CodeAnalysis.DiagnosticSeverity.Error).Select(d => d.GetMessage()))}");
+
+ // Verify generated code exists
+ Assert.NotNull(generated);
+
+ // Verify RelativeBindingSource is generated correctly with FindAncestor mode (ContentPage is an Element)
+ Assert.Contains("RelativeBindingSource", generated, StringComparison.Ordinal);
+ Assert.Contains("FindAncestor", generated, StringComparison.Ordinal);
+ Assert.Contains("ContentPage", generated, StringComparison.Ordinal);
+ }
+
+ [Fact]
+ public void SelfPathWithFindAncestorCustomTypeGeneratesValidCode()
+ {
+ // Test for regression: RelativeSource AncestorType with Path=. and custom page type
+ // This replicates the user's scenario where MainPage is a custom type
+ var xaml =
+"""
+
+
+
+
+
+
+
+
+""";
+
+ var code =
+"""
+#nullable enable
+using System.Windows.Input;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui.Controls.Xaml;
+
+namespace Test;
+
+[XamlProcessing(XamlInflator.SourceGen)]
+public partial class ChildView : ContentView
+{
+ public ChildView()
+ {
+ InitializeComponent();
+ BindingContext = this;
+ }
+
+ public ICommand Navigate => new Command((param) => { });
+}
+
+public class MainPage : ContentPage
+{
+}
+""";
+
+ var (result, generated) = RunGenerator(xaml, code);
+
+ // Verify no diagnostics
+ Assert.False(result.Diagnostics.Any(d => d.Severity == Microsoft.CodeAnalysis.DiagnosticSeverity.Error),
+ $"Generator produced errors: {string.Join(", ", result.Diagnostics.Where(d => d.Severity == Microsoft.CodeAnalysis.DiagnosticSeverity.Error).Select(d => d.GetMessage()))}");
+
+ // Verify generated code exists
+ Assert.NotNull(generated);
+
+ // Verify RelativeBindingSource is generated correctly with FindAncestor mode and custom type
+ Assert.Contains("RelativeBindingSource", generated, StringComparison.Ordinal);
+ Assert.Contains("FindAncestor", generated, StringComparison.Ordinal);
+ Assert.Contains("Test.MainPage", generated, StringComparison.Ordinal);
+ }
}
diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Maui33247.xaml b/src/Controls/tests/Xaml.UnitTests/Issues/Maui33247.xaml
new file mode 100644
index 000000000000..0dc7503b345f
--- /dev/null
+++ b/src/Controls/tests/Xaml.UnitTests/Issues/Maui33247.xaml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Maui33247.xaml.cs b/src/Controls/tests/Xaml.UnitTests/Issues/Maui33247.xaml.cs
new file mode 100644
index 000000000000..60a2ac9c0f52
--- /dev/null
+++ b/src/Controls/tests/Xaml.UnitTests/Issues/Maui33247.xaml.cs
@@ -0,0 +1,46 @@
+using System.Windows.Input;
+using Xunit;
+
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+///
+/// Test for regression: RelativeSource AncestorType with Path=. returns null in v10.0.20
+///
+/// Scenario: A TapGestureRecognizer tries to get a reference to the parent ContentPage via
+/// CommandParameter="{Binding Path=., Source={RelativeSource AncestorType={x:Type local:Maui33247}}}"
+///
+/// Expected: The CommandParameter should receive the ContentPage reference.
+/// Bug: In v10.0.20, the CommandParameter was null when using SourceGen because the source generator
+/// was incorrectly compiling the binding to use x:DataType as the source type instead of allowing
+/// the RelativeSource to resolve the source at runtime.
+///
+/// Fix: When a binding has a Source property with a RelativeSource, skip the compiled binding path
+/// and use the fallback string-based binding instead.
+///
+public partial class Maui33247 : ContentPage
+{
+ public Maui33247()
+ {
+ InitializeComponent();
+ BindingContext = this;
+ }
+
+ public ICommand Navigate => new Command((param) => { });
+
+ [Collection("Issue")]
+ public class Tests
+ {
+ [Theory]
+ [XamlInflatorData]
+ internal void DirectRelativeSourceAncestorTypeWithSelfPathReturnsAncestor(XamlInflator inflator)
+ {
+ // Test that RelativeSource AncestorType with Path=. returns the ancestor object for direct children
+ var page = new Maui33247(inflator);
+
+ // The CommandParameter with RelativeSource AncestorType and Path=. should be the ContentPage itself
+ Assert.NotNull(page.DirectTapGesture.CommandParameter);
+ Assert.IsType(page.DirectTapGesture.CommandParameter);
+ Assert.Same(page, page.DirectTapGesture.CommandParameter);
+ }
+ }
+}