From 2aa6dd071772280c5cf81d4b2b973c36d7b508e6 Mon Sep 17 00:00:00 2001 From: BagavathiPerumal Date: Tue, 10 Mar 2026 20:39:30 +0530 Subject: [PATCH 1/2] =?UTF-8?q?fix-34056=20=E2=80=93=20Updated=20SourceGen?= =?UTF-8?q?=20to=20generate=20compiled=20bindings=20for=20RelativeSource?= =?UTF-8?q?=20AncestorType=20(trim-safe=20under=20AOT),=20while=20preservi?= =?UTF-8?q?ng=20the=20string-based=20Binding=20fallback=20for=20other=20Re?= =?UTF-8?q?lativeSource=20modes=20where=20the=20source=20type=20is=20resol?= =?UTF-8?q?ved=20at=20runtime.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Controls/src/SourceGen/KnownMarkups.cs | 83 ++++++++++++++--- .../Xaml.UnitTests/Issues/Maui34056.xaml | 33 +++++++ .../Xaml.UnitTests/Issues/Maui34056.xaml.cs | 89 +++++++++++++++++++ 3 files changed, 194 insertions(+), 11 deletions(-) create mode 100644 src/Controls/tests/Xaml.UnitTests/Issues/Maui34056.xaml create mode 100644 src/Controls/tests/Xaml.UnitTests/Issues/Maui34056.xaml.cs diff --git a/src/Controls/src/SourceGen/KnownMarkups.cs b/src/Controls/src/SourceGen/KnownMarkups.cs index 195b6554023e..386d7418ae34 100644 --- a/src/Controls/src/SourceGen/KnownMarkups.cs +++ b/src/Controls/src/SourceGen/KnownMarkups.cs @@ -343,20 +343,37 @@ private static bool ProvideValueForBindingExtension(ElementNode markupNode, Inde returnType = context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.BindingBase")!; ITypeSymbol? dataTypeSymbol = null; - // When Source is RelativeSource, the type is determined at runtime — skip compilation. - // When Source is x:Reference, resolve the referenced element's type and compile against it. - // Otherwise, use x:DataType from the current scope. - bool hasRelativeSource = HasRelativeSourceBinding(markupNode); - context.Variables.TryGetValue(markupNode, out ILocalValue? extVariable); - if ( !hasRelativeSource - && extVariable is not null) + if (extVariable is not null) { - ITypeSymbol? xRefSourceType = TryResolveXReferenceSourceType(markupNode, context); - dataTypeSymbol = xRefSourceType; - if (dataTypeSymbol is null) - TryGetXDataType(markupNode, context, out dataTypeSymbol); + // Determine the source type for compiled binding based on the binding's Source configuration: + // + // 1. RelativeSource with a resolvable AncestorType: use the AncestorType as the source + // type. The symbol is already registered in context.Types by + // ProvideValueForRelativeSourceExtension, enabling trim-safe TypedBinding generation. + // + // 2. RelativeSource without AncestorType (Self, TemplatedParent, or FindAncestor without + // a type): the binding source is resolved at runtime. Using x:DataType as the source + // type here would produce a compiled binding with an incorrect source type, leading to + // runtime failures. Fall through to the string-based Binding path instead. + // + // 3. x:Reference: resolve the referenced element's type and compile against it. + // + // 4. No explicit source: use x:DataType if available to produce a compiled TypedBinding. + ITypeSymbol? xRefSourceType = null; + if (TryGetRelativeSourceAncestorType(markupNode, context, out var ancestorTypeSymbol) + && ancestorTypeSymbol is not null) + { + dataTypeSymbol = ancestorTypeSymbol; + } + else if (!HasRelativeSourceBinding(markupNode)) + { + xRefSourceType = TryResolveXReferenceSourceType(markupNode, context); + dataTypeSymbol = xRefSourceType; + if (dataTypeSymbol is null) + TryGetXDataType(markupNode, context, out dataTypeSymbol); + } if (dataTypeSymbol is not null) { @@ -708,6 +725,50 @@ static bool HasRelativeSourceBinding(ElementNode bindingNode) return null; } + + // Checks if the binding has a Source property that is a RelativeSource extension + // with a resolvable AncestorType. If so, returns the already-resolved AncestorType + // symbol from context.Types (populated earlier by ProvideValueForRelativeSourceExtension). + // This allows AncestorType bindings to use the compiled (trim-safe) TypedBinding path. + static bool TryGetRelativeSourceAncestorType(ElementNode bindingNode, SourceGenContext context, out ITypeSymbol? ancestorType) + { + ancestorType = null; + + // 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 not ElementNode relativeSourceNode + || (relativeSourceNode.XmlType.Name != "RelativeSourceExtension" + && relativeSourceNode.XmlType.Name != "RelativeSource")) + { + return false; + } + + // Find the AncestorType property on the RelativeSource node + if (!relativeSourceNode.Properties.TryGetValue(new XmlName("", "AncestorType"), out INode? ancestorTypeNode) + && !relativeSourceNode.Properties.TryGetValue(new XmlName(null, "AncestorType"), out ancestorTypeNode)) + relativeSourceNode.Properties.TryGetValue(new XmlName(XamlParser.MauiUri, "AncestorType"), out ancestorTypeNode); + + if (ancestorTypeNode is null) + { + return false; + } + + // The AncestorType is typically an x:Type extension (ElementNode). + // ProvideValueForRelativeSourceExtension already resolved this type + // and registered it in context.Types — just look it up. + if (ancestorTypeNode is ElementNode typeExtNode) + { + return context.Types.TryGetValue(typeExtNode, out ancestorType) && ancestorType is not null; + } + + 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/Xaml.UnitTests/Issues/Maui34056.xaml b/src/Controls/tests/Xaml.UnitTests/Issues/Maui34056.xaml new file mode 100644 index 000000000000..32eec6b74e31 --- /dev/null +++ b/src/Controls/tests/Xaml.UnitTests/Issues/Maui34056.xaml @@ -0,0 +1,33 @@ + + + + + + + +