Skip to content

Commit 64d777d

Browse files
[XSG] Fix RelativeSource binding with FindAncestorBindingContext mode (dotnet#32925)
* Initial plan * Add RelativeSourceExtension support to XSG for FindAncestorBindingContext mode Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com> * Address code review feedback for improved error messages and simplified test Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com> * Add SourceGen.UnitTests for RelativeSource bindings to ensure no XamlTypeResolver usage Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
1 parent 7c83bad commit 64d777d

File tree

5 files changed

+468
-0
lines changed

5 files changed

+468
-0
lines changed

src/Controls/src/SourceGen/KnownMarkups.cs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,107 @@ public static bool ProvideValueForTypeExtension(ElementNode node, IndentedTextWr
129129
return false;
130130
}
131131

132+
public static bool ProvideValueForRelativeSourceExtension(ElementNode markupNode, IndentedTextWriter writer, SourceGenContext context, NodeSGExtensions.GetNodeValueDelegate? getNodeValue, out ITypeSymbol? returnType, out string value)
133+
{
134+
returnType = context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.RelativeBindingSource")!;
135+
136+
// Get the Mode property value
137+
string? modeStr = null;
138+
if (markupNode.Properties.TryGetValue(new XmlName("", "Mode"), out INode? modeNode)
139+
|| markupNode.Properties.TryGetValue(new XmlName(null, "Mode"), out modeNode))
140+
{
141+
modeStr = (modeNode as ValueNode)?.Value as string;
142+
}
143+
else if (markupNode.CollectionItems.Count == 1)
144+
{
145+
// ContentProperty is Mode
146+
modeStr = (markupNode.CollectionItems[0] as ValueNode)?.Value as string;
147+
}
148+
149+
// Get the AncestorType property (which is an x:Type extension)
150+
ITypeSymbol? ancestorTypeSymbol = null;
151+
if (markupNode.Properties.TryGetValue(new XmlName("", "AncestorType"), out INode? ancestorTypeNode)
152+
|| markupNode.Properties.TryGetValue(new XmlName(null, "AncestorType"), out ancestorTypeNode))
153+
{
154+
if (ancestorTypeNode is ElementNode typeExtNode && context.Types.TryGetValue(typeExtNode, out ancestorTypeSymbol))
155+
{
156+
// Type was already resolved by ProvideValueForTypeExtension
157+
}
158+
else if (ancestorTypeNode is ValueNode vnType)
159+
{
160+
// Try to parse as a type name
161+
var typeName = vnType.Value as string;
162+
if (!IsNullOrEmpty(typeName))
163+
{
164+
XmlType xmlType = TypeArgumentsParser.ParseSingle(typeName!, markupNode.NamespaceResolver, markupNode as IXmlLineInfo);
165+
xmlType.TryResolveTypeSymbol(null, context.Compilation, context.XmlnsCache, context.TypeCache, out var resolvedType);
166+
ancestorTypeSymbol = resolvedType;
167+
}
168+
}
169+
}
170+
171+
// Get the AncestorLevel property (default is 1)
172+
int ancestorLevel = 1;
173+
if (markupNode.Properties.TryGetValue(new XmlName("", "AncestorLevel"), out INode? ancestorLevelNode)
174+
|| markupNode.Properties.TryGetValue(new XmlName(null, "AncestorLevel"), out ancestorLevelNode))
175+
{
176+
if (ancestorLevelNode is ValueNode vnLevel && int.TryParse(vnLevel.Value as string, out int level))
177+
{
178+
ancestorLevel = level;
179+
}
180+
}
181+
182+
// Determine the actual mode
183+
if (ancestorTypeSymbol is not null)
184+
{
185+
string actualMode;
186+
if (modeStr == "FindAncestor" || modeStr == "FindAncestorBindingContext")
187+
{
188+
actualMode = $"global::Microsoft.Maui.Controls.RelativeBindingSourceMode.{modeStr}";
189+
}
190+
else
191+
{
192+
// When Mode is not explicitly FindAncestor or FindAncestorBindingContext,
193+
// determine based on whether the type is an Element
194+
var elementType = context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.Element");
195+
if (elementType is not null && context.Compilation.HasImplicitConversion(ancestorTypeSymbol, elementType))
196+
{
197+
actualMode = "global::Microsoft.Maui.Controls.RelativeBindingSourceMode.FindAncestor";
198+
}
199+
else
200+
{
201+
actualMode = "global::Microsoft.Maui.Controls.RelativeBindingSourceMode.FindAncestorBindingContext";
202+
}
203+
}
204+
205+
value = $"new global::Microsoft.Maui.Controls.RelativeBindingSource({actualMode}, typeof({ancestorTypeSymbol.ToFQDisplayString()}), {ancestorLevel})";
206+
return true;
207+
}
208+
else if (modeStr == "Self")
209+
{
210+
value = "global::Microsoft.Maui.Controls.RelativeBindingSource.Self";
211+
return true;
212+
}
213+
else if (modeStr == "TemplatedParent")
214+
{
215+
value = "global::Microsoft.Maui.Controls.RelativeBindingSource.TemplatedParent";
216+
return true;
217+
}
218+
else if (modeStr == "FindAncestor" || modeStr == "FindAncestorBindingContext")
219+
{
220+
// These modes require AncestorType
221+
context.ReportDiagnostic(Diagnostic.Create(Descriptors.XamlParserError, null, $"RelativeSource Mode '{modeStr}' requires AncestorType"));
222+
value = "default";
223+
return false;
224+
}
225+
else
226+
{
227+
context.ReportDiagnostic(Diagnostic.Create(Descriptors.XamlParserError, null, $"Invalid RelativeSource Mode '{modeStr ?? "(none)"}'"));
228+
value = "default";
229+
return false;
230+
}
231+
}
232+
132233
public static bool ProvideValueForDynamicResourceExtension(ElementNode markupNode, IndentedTextWriter writer, SourceGenContext context, NodeSGExtensions.GetNodeValueDelegate? getNodeValue, out ITypeSymbol? returnType, out string value)
133234
{
134235
returnType = context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.Internals.DynamicResource")!;

src/Controls/src/SourceGen/NodeSGExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ public static Dictionary<ITypeSymbol, ProvideValueDelegate> GetKnownEarlyMarkupE
9898
{context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.Xaml.StyleSheetExtension")!, KnownMarkups.ProvideValueForStyleSheetExtension},
9999
{context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.Xaml.TypeExtension")!, KnownMarkups.ProvideValueForTypeExtension},
100100
{context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.Xaml.DataTemplateExtension")!, KnownMarkups.ProvideValueForDataTemplateExtension},
101+
{context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.Xaml.RelativeSourceExtension")!, KnownMarkups.ProvideValueForRelativeSourceExtension},
101102
};
102103

103104
// These markup extensions can only provide values late once the properties have their final values

0 commit comments

Comments
 (0)