Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 172 additions & 25 deletions src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -524,43 +524,65 @@ static bool TryCompileBindingPath(ElementNode node, ILContext context, VariableD
n = GetParent(n);
}

if (dataTypeNode is null)
{
context.LoggingHelper.LogWarningOrError(BindingWithoutDataType, context.XamlFilePath, node.LineNumber, node.LinePosition, 0, 0, null);
bool xDataTypeIsOnBindingNode = n == node;

if (IsRelativeSourceWithoutAncestorType(node, context, module))
{
return false;
}

if (xDataTypeIsInOuterScope)
TypeReference tSourceRef = null;
if (HasRelativeOrReferenceSource(node) && !xDataTypeIsOnBindingNode)
{
context.LoggingHelper.LogWarningOrError(BindingWithXDataTypeFromOuterScope, context.XamlFilePath, node.LineNumber, node.LinePosition, 0, 0, null);
// continue compilation
if (!TryGetRelativeSourceAncestorTypeReference(node, context, module, out tSourceRef))
{
return false;
}
}

if ( dataTypeNode is ElementNode enode
&& enode.XmlType.NamespaceUri == XamlParser.X2009Uri
&& enode.XmlType.Name == nameof(Xaml.NullExtension))
if (tSourceRef is null)
{
context.LoggingHelper.LogWarningOrError(BindingWithNullDataType, context.XamlFilePath, node.LineNumber, node.LinePosition, 0, 0, null);
return false;
}
if (dataTypeNode is null)
{
context.LoggingHelper.LogWarningOrError(BindingWithoutDataType, context.XamlFilePath, node.LineNumber, node.LinePosition, 0, 0, null);

if ((dataTypeNode as ValueNode)?.Value is not string dataType)
throw new BuildException(XDataTypeSyntax, dataTypeNode as IXmlLineInfo, null);
return false;
}

XmlType dtXType = null;
try
{
dtXType = TypeArgumentsParser.ParseSingle(dataType, node.NamespaceResolver, dataTypeNode as IXmlLineInfo)
?? throw new BuildException(XDataTypeSyntax, dataTypeNode as IXmlLineInfo, null);
}
catch (XamlParseException)
{
var prefix = dataType.Contains(":") ? dataType.Substring(0, dataType.IndexOf(":", StringComparison.Ordinal)) : "";
throw new BuildException(XmlnsUndeclared, dataTypeNode as IXmlLineInfo, null, prefix);
if (xDataTypeIsInOuterScope)
{
context.LoggingHelper.LogWarningOrError(BindingWithXDataTypeFromOuterScope, context.XamlFilePath, node.LineNumber, node.LinePosition, 0, 0, null);
// continue compilation
}

if ( dataTypeNode is ElementNode enode
&& enode.XmlType.NamespaceUri == XamlParser.X2009Uri
&& enode.XmlType.Name == nameof(Xaml.NullExtension))
{
context.LoggingHelper.LogWarningOrError(BindingWithNullDataType, context.XamlFilePath, node.LineNumber, node.LinePosition, 0, 0, null);
return false;
}

if ((dataTypeNode as ValueNode)?.Value is not string dataType)
throw new BuildException(XDataTypeSyntax, dataTypeNode as IXmlLineInfo, null);

XmlType dtXType = null;
try
{
dtXType = TypeArgumentsParser.ParseSingle(dataType, node.NamespaceResolver, dataTypeNode as IXmlLineInfo)
?? throw new BuildException(XDataTypeSyntax, dataTypeNode as IXmlLineInfo, null);
}
catch (XamlParseException)
{
var prefix = dataType.Contains(":") ? dataType.Substring(0, dataType.IndexOf(":", StringComparison.Ordinal)) : "";
throw new BuildException(XmlnsUndeclared, dataTypeNode as IXmlLineInfo, null, prefix);
}

tSourceRef = dtXType.GetTypeReference(context.Cache, module, (IXmlLineInfo)node);
if (tSourceRef == null)
return false;
}

var tSourceRef = dtXType.GetTypeReference(context.Cache, module, (IXmlLineInfo)node);
if (tSourceRef == null)
return false; //throw

Expand Down Expand Up @@ -640,6 +662,131 @@ static bool IsBindingContextBinding(ElementNode node)
&& propertyName.LocalName == nameof(BindableObject.BindingContext);
}

// Returns true when the binding's Source property is a RelativeSource or x:Reference.
// When Source is a RelativeSource or x:Reference, x:DataType inherited from an
// ancestor DataTemplate describes template items — not the actual binding source type.
// This matches the behaviour in KnownMarkups.ProvideValueForBindingExtension (SourceGen)
// and the runtime BindingExtension.ProvideValue (which already sets DataType=null for
// all non-RelativeBindingSource sources, including x:Reference resolved objects).
static bool HasRelativeOrReferenceSource(ElementNode bindingNode)
{
if (!bindingNode.Properties.TryGetValue(new XmlName("", "Source"), out INode sourceNode)
&& !bindingNode.Properties.TryGetValue(new XmlName(null, "Source"), out sourceNode))
{
return false;
}

return sourceNode is ElementNode sourceElementNode
&& sourceElementNode.XmlType.Name is "RelativeSourceExtension"
or "RelativeSource"
or "ReferenceExtension"
or "Reference";
}

static bool IsRelativeSourceWithoutAncestorType(ElementNode bindingNode, ILContext context, ModuleDefinition module)
{
if (!TryGetRelativeSourceNode(bindingNode, out var relativeSourceNode))
{
return false;
}

return !TryGetRelativeSourceAncestorTypeReference(bindingNode, context, module, out _);
}

static bool TryGetRelativeSourceAncestorTypeReference(ElementNode bindingNode, ILContext context, ModuleDefinition module, out TypeReference ancestorType)
{
ancestorType = null;

if (!TryGetRelativeSourceNode(bindingNode, out var relativeSourceNode))
{
return false;
}

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))
{
return false;
}

if (ancestorTypeNode is ElementNode ancestorTypeElement)
{
if (context.TypeExtensions.TryGetValue(ancestorTypeElement, out var mappedType))
{
ancestorType = module.ImportReference(mappedType);
return true;
}

if (!TryGetTypeNameFromTypeExtensionNode(ancestorTypeElement, out var typeName))
{
return false;
}

return TryResolveTypeFromName(typeName, ancestorTypeElement, context, module, out ancestorType);
}

if (ancestorTypeNode is ValueNode { Value: string directTypeName })
{
return TryResolveTypeFromName(directTypeName, bindingNode, context, module, out ancestorType);
}

return false;
}

static bool TryGetRelativeSourceNode(ElementNode bindingNode, out ElementNode relativeSourceNode)
{
relativeSourceNode = null;

if ((!bindingNode.Properties.TryGetValue(new XmlName("", "Source"), out INode sourceNode)
&& !bindingNode.Properties.TryGetValue(new XmlName(null, "Source"), out sourceNode))
|| sourceNode is not ElementNode sourceElementNode)
{
return false;
}

if (sourceElementNode.XmlType.Name is not "RelativeSourceExtension" and not "RelativeSource")
{
return false;
}

relativeSourceNode = sourceElementNode;
return true;
}

static bool TryGetTypeNameFromTypeExtensionNode(ElementNode typeNode, out string typeName)
{
typeName = null;

if (!typeNode.Properties.TryGetValue(new XmlName("", "TypeName"), out INode typeNameNode)
&& !typeNode.Properties.TryGetValue(new XmlName(null, "TypeName"), out typeNameNode)
&& !typeNode.Properties.TryGetValue(new XmlName(XamlParser.MauiUri, "TypeName"), out typeNameNode)
&& typeNode.CollectionItems.Count == 1)
{
typeNameNode = typeNode.CollectionItems[0];
}

typeName = (typeNameNode as ValueNode)?.Value as string;
return !string.IsNullOrEmpty(typeName);
}

static bool TryResolveTypeFromName(string typeName, ElementNode namespaceNode, ILContext context, ModuleDefinition module, out TypeReference typeReference)
{
typeReference = null;

XmlType xmlType;
try
{
xmlType = TypeArgumentsParser.ParseSingle(typeName, namespaceNode.NamespaceResolver, namespaceNode as IXmlLineInfo);
}
catch (XamlParseException)
{
return false;
}

typeReference = xmlType?.GetTypeReference(context.Cache, module, namespaceNode as IXmlLineInfo);
return typeReference is not null;
}

bool DoesNotInheritDataType(ElementNode node)
{
return GetParent(node) is ElementNode parentNode
Expand Down
116 changes: 108 additions & 8 deletions src/Controls/src/SourceGen/KnownMarkups.cs
Original file line number Diff line number Diff line change
Expand Up @@ -340,17 +340,26 @@ private static bool ProvideValueForBindingExtension(ElementNode markupNode, Inde
{
returnType = context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.BindingBase")!;
ITypeSymbol? dataTypeSymbol = null;

// When Source is explicitly set (RelativeSource or x:Reference), x:DataType does not describe
// the actual source — skip compilation and fall back to runtime binding.

// When Source is explicitly set, inherited x:DataType usually does not describe the actual source.
// RelativeSource with AncestorType is the exception: AncestorType defines the source type.
// RelativeSource Self should never compile to TypedBinding because the source is the view itself.
bool hasExplicitSource = HasExplicitBindingSource(markupNode);

bool xDataTypeOnBindingNode = markupNode.Properties.ContainsKey(XmlName.xDataType);
bool isRelativeSourceWithoutAncestorType = IsRelativeSourceWithoutAncestorType(markupNode, context);

context.Variables.TryGetValue(markupNode, out ILocalValue? extVariable);

if ( !hasExplicitSource
&& extVariable is not null)

if (!isRelativeSourceWithoutAncestorType && extVariable is not null)
{
TryGetXDataType(markupNode, context, out dataTypeSymbol);
if (!hasExplicitSource || xDataTypeOnBindingNode)
{
TryGetXDataType(markupNode, context, out dataTypeSymbol);
}
else if (TryGetRelativeSourceAncestorType(markupNode, context, out var ancestorType))
{
dataTypeSymbol = ancestorType;
}

if (dataTypeSymbol is not null)
{
Expand Down Expand Up @@ -629,6 +638,97 @@ static bool IsBindingContextBinding(ElementNode node)
&& propertyName.LocalName == "BindingContext";
}

static bool IsRelativeSourceWithoutAncestorType(ElementNode bindingNode, SourceGenContext context)
{
if (!TryGetRelativeSourceNode(bindingNode, out var relativeSourceNode))
{
return false;
}

return !TryGetRelativeSourceAncestorType(bindingNode, context, out _);
}

static bool TryGetRelativeSourceAncestorType(ElementNode bindingNode, SourceGenContext context, out ITypeSymbol? ancestorType)
{
ancestorType = null;

if (!TryGetRelativeSourceNode(bindingNode, out var relativeSourceNode))
{
return false;
}

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))
{
return false;
}

if (ancestorTypeNode is ElementNode typeExtNode)
{
if (context.Types.TryGetValue(typeExtNode, out var resolvedType))
{
ancestorType = resolvedType;
return true;
}

if (!typeExtNode.Properties.TryGetValue(new XmlName("", "TypeName"), out INode? typeNameNode)
&& !typeExtNode.Properties.TryGetValue(new XmlName(null, "TypeName"), out typeNameNode)
&& !typeExtNode.Properties.TryGetValue(new XmlName(XamlParser.MauiUri, "TypeName"), out typeNameNode)
&& typeExtNode.CollectionItems.Count == 1)
{
typeNameNode = typeExtNode.CollectionItems[0];
}

if (typeNameNode is ValueNode { Value: string typeName } && !IsNullOrEmpty(typeName))
{
XmlType xmlType = TypeArgumentsParser.ParseSingle(typeName, typeExtNode.NamespaceResolver, typeExtNode as IXmlLineInfo);
if (xmlType.TryResolveTypeSymbol(null, context.Compilation, context.XmlnsCache, context.TypeCache, out var resolvedAncestorType)
&& resolvedAncestorType is not null)
{
ancestorType = resolvedAncestorType;
context.Types[typeExtNode] = resolvedAncestorType;
return true;
}
}

return false;
}

if (ancestorTypeNode is ValueNode { Value: string directTypeName } && !IsNullOrEmpty(directTypeName))
{
XmlType xmlType = TypeArgumentsParser.ParseSingle(directTypeName, bindingNode.NamespaceResolver, bindingNode as IXmlLineInfo);
if (xmlType.TryResolveTypeSymbol(null, context.Compilation, context.XmlnsCache, context.TypeCache, out var resolvedAncestorType)
&& resolvedAncestorType is not null)
{
ancestorType = resolvedAncestorType;
return true;
}
}

return false;
}

static bool TryGetRelativeSourceNode(ElementNode bindingNode, out ElementNode relativeSourceNode)
{
relativeSourceNode = null!;

if ((!bindingNode.Properties.TryGetValue(new XmlName("", "Source"), out INode? sourceNode)
&& !bindingNode.Properties.TryGetValue(new XmlName(null, "Source"), out sourceNode))
|| sourceNode is not ElementNode sourceElementNode)
{
return false;
}

if (sourceElementNode.XmlType.Name is not "RelativeSourceExtension" and not "RelativeSource")
{
return false;
}

relativeSourceNode = sourceElementNode;
return true;
}

// Checks if the binding has a Source property set to RelativeSource or x:Reference.
// When Source is explicitly set, x:DataType does not describe the actual binding source,
// so we should NOT compile the binding using x:DataType.
Expand Down
Loading
Loading