@ -18,10 +18,17 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
private const string AvaloniaNs = "https://github.com/avaloniaui" ;
public IXamlIlAstNode Transform ( XamlIlAstTransformationContext context , IXamlIlAstNode node )
{
if ( context . ParentNodes ( ) . FirstOrDefault ( ) is AvaloniaXamlIlDataContextTypeMetadataNode )
{
// We've already resolved the data context type for this node.
return node ;
}
if ( node is XamlIlAstObjectNode on )
{
AvaloniaXamlIlDataContextTypeMetadataNode calculatedDataContextTypeNode = null ;
AvaloniaXamlIlDataContextTypeMetadataNode inferr edDataContextTypeNode = null ;
AvaloniaXamlIlDataContextTypeMetadataNode directiveDataContextTypeNode = null ;
bool isDataTemplate = on . Type . GetClrType ( ) . Equals ( context . GetAvaloniaTypes ( ) . DataTemplate ) ;
for ( int i = 0 ; i < on . Children . Count ; + + i )
{
@ -45,46 +52,123 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
}
}
else if ( child is XamlIlAstXamlPropertyValueNode pv
& & pv . Property is XamlIlAstNamePropertyReference pref
& & pref . Name = = "DataContext"
& & pref . DeclaringType is XamlIlAstXmlTypeReference tref
& & tref . Name = = "StyledElement"
& & tref . XmlNamespace = = AvaloniaNs )
else if ( child is XamlIlPropertyAssignmentNode pa )
{
var bindingType = context . GetAvaloniaTypes ( ) . IBinding ;
if ( ! pv . Values [ 0 ] . Type . GetClrType ( ) . GetAllInterfaces ( ) . Contains ( bindingType ) )
if ( pa . Property . Name = = "DataContext"
& & pa . Property . DeclaringType . Equals ( context . GetAvaloniaTypes ( ) . StyledElement )
& & pa . Values [ 0 ] is XamlIlMarkupExtensionNode ext
& & ext . Value is XamlIlAstObjectNode obj )
{
calculatedDataContextTypeNode = new AvaloniaXamlIlDataContextTypeMetadataNode ( on , pv . Values [ 0 ] . Type . GetClrType ( ) ) ;
inferredDataContextTypeNode = ParseDataContext ( context , on , obj ) ;
}
else if ( pv . Values [ 0 ] . Type . GetClrType ( ) . Equals ( context . GetAvaloniaTypes ( ) . CompiledBindingExtension )
& & pv . Values [ 0 ] is XamlIlAstObjectNode binding )
else if ( isDataTemplate
& & pa . Property . Name = = "DataType"
& & pa . Values [ 0 ] is XamlIlTypeExtensionNode dataTypeNode )
{
IXamlIlType startType ;
var parentDataContextNode = context . ParentNodes ( ) . OfType < AvaloniaXamlIlDataContextTypeMetadataNode > ( ) . FirstOrDefault ( ) ;
if ( parentDataContextNode is null )
{
throw new XamlIlParseException ( "Cannot parse a compiled binding without an explicit x:DataContextType directive to give a starting data type for bindings." , binding ) ;
}
startType = parentDataContextNode . DataContextType ;
inferredDataContextTypeNode = new AvaloniaXamlIlDataContextTypeMetadataNode ( on , dataTypeNode . Value . GetClrType ( ) ) ;
}
}
}
var bindingResultType = XamlIlBindingPathHelper . UpdateCompiledBindingExtension ( context , binding , startType ) ;
calculatedDataContextTypeNode = new AvaloniaXamlIlDataContextTypeMetadataNode ( on , bindingResultType ) ;
// If there is no x:DataContextType directive,
// do more specialized inference
if ( directiveDataContextTypeNode is null )
{
if ( isDataTemplate & & inferredDataContextTypeNode is null )
{
// Infer data type from collection binding on a control that displays items.
var parentObject = context . ParentNodes ( ) . OfType < XamlIlAstObjectNode > ( ) . FirstOrDefault ( ) ;
if ( parentObject ! = null & & context . GetAvaloniaTypes ( ) . IItemsPresenterHost . IsDirectlyAssignableFrom ( parentObject . Type . GetClrType ( ) ) )
{
inferredDataContextTypeNode = InferDataContextOfPresentedItem ( context , on , parentObject ) ;
}
else
{
inferredDataContextTypeNode = new AvaloniaXamlIlUninferrableDataContextMetadataNode ( on ) ;
}
}
}
return directiveDataContextTypeNode ? ? calculatedDataContextTypeNode ? ? node ;
return directiveDataContextTypeNode ? ? inferredDataContextTypeNode ? ? node ;
}
// TODO: Add node for DataTemplate scope.
return node ;
}
private static AvaloniaXamlIlDataContextTypeMetadataNode InferDataContextOfPresentedItem ( XamlIlAstTransformationContext context , XamlIlAstObjectNode on , XamlIlAstObjectNode parentObject )
{
var parentItemsValue = parentObject
. Children . OfType < XamlIlPropertyAssignmentNode > ( )
. FirstOrDefault ( pa = > pa . Property . Name = = "Items" )
? . Values [ 0 ] ;
if ( parentItemsValue is null )
{
// We can't infer the collection type and the currently calculated type is definitely wrong.
// Notify the user that we were unable to infer the data context type if they use a compiled binding.
return new AvaloniaXamlIlUninferrableDataContextMetadataNode ( on ) ;
}
IXamlIlType itemsCollectionType = null ;
if ( context . GetAvaloniaTypes ( ) . IBinding . IsAssignableFrom ( parentItemsValue . Type . GetClrType ( ) ) )
{
if ( parentItemsValue . Type . GetClrType ( ) . Equals ( context . GetAvaloniaTypes ( ) . CompiledBindingExtension )
& & parentItemsValue is XamlIlMarkupExtensionNode ext & & ext . Value is XamlIlAstObjectNode parentItemsBinding )
{
var parentItemsDataContext = context . ParentNodes ( ) . SkipWhile ( n = > n ! = parentObject ) . OfType < AvaloniaXamlIlDataContextTypeMetadataNode > ( ) . FirstOrDefault ( ) ;
if ( parentItemsDataContext ! = null )
{
itemsCollectionType = XamlIlBindingPathHelper . UpdateCompiledBindingExtension ( context , parentItemsBinding , parentItemsDataContext . DataContextType ) ;
}
}
}
else
{
itemsCollectionType = parentItemsValue . Type . GetClrType ( ) ;
}
if ( itemsCollectionType ! = null )
{
var elementType = itemsCollectionType
. GetAllInterfaces ( )
. FirstOrDefault ( i = >
i . GenericTypeDefinition ? . Equals ( context . Configuration . WellKnownTypes . IEnumerableT ) = = true )
. GenericArguments [ 0 ] ;
return new AvaloniaXamlIlDataContextTypeMetadataNode ( on , elementType ) ;
}
// We can't infer the collection type and the currently calculated type is definitely wrong.
// Notify the user that we were unable to infer the data context type if they use a compiled binding.
return new AvaloniaXamlIlUninferrableDataContextMetadataNode ( on ) ;
}
private static AvaloniaXamlIlDataContextTypeMetadataNode ParseDataContext ( XamlIlAstTransformationContext context , XamlIlAstObjectNode on , XamlIlAstObjectNode obj )
{
var bindingType = context . GetAvaloniaTypes ( ) . IBinding ;
if ( ! bindingType . IsAssignableFrom ( obj . Type . GetClrType ( ) ) )
{
return new AvaloniaXamlIlDataContextTypeMetadataNode ( on , obj . Type . GetClrType ( ) ) ;
}
else if ( obj . Type . GetClrType ( ) . Equals ( context . GetAvaloniaTypes ( ) . CompiledBindingExtension ) )
{
IXamlIlType startType ;
var parentDataContextNode = context . ParentNodes ( ) . OfType < AvaloniaXamlIlDataContextTypeMetadataNode > ( ) . FirstOrDefault ( ) ;
if ( parentDataContextNode is null )
{
throw new XamlIlParseException ( "Cannot parse a compiled binding without an explicit x:DataContextType directive to give a starting data type for bindings." , obj ) ;
}
startType = parentDataContextNode . DataContextType ;
var bindingResultType = XamlIlBindingPathHelper . UpdateCompiledBindingExtension ( context , obj , startType ) ;
return new AvaloniaXamlIlDataContextTypeMetadataNode ( on , bindingResultType ) ;
}
return null ;
}
}
class AvaloniaXamlIlDataContextTypeMetadataNode : XamlIlValueWithSideEffectNodeBase
{
public IXamlIlType DataContextType { get ; set ; }
public virtual IXamlIlType DataContextType { get ; }
public AvaloniaXamlIlDataContextTypeMetadataNode ( IXamlIlAstValueNode value , IXamlIlType targetType )
: base ( value , value )
@ -92,4 +176,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
DataContextType = targetType ;
}
}
class AvaloniaXamlIlUninferrableDataContextMetadataNode : AvaloniaXamlIlDataContextTypeMetadataNode
{
public AvaloniaXamlIlUninferrableDataContextMetadataNode ( IXamlIlAstValueNode value )
: base ( value , null )
{
}
public override IXamlIlType DataContextType = > throw new XamlIlTransformException ( "Unable to infer DataContext type for compiled bindings nested within this element." , Value ) ;
}
}