Avalonia
Avalonia copied to clipboard
[Compiled Bindings] NRE when trying to bind to a generic property
Describe the bug
The XAML compiler currently throws an NRE when trying to bind to a generic property:
Internal compiler error while emitting node Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.XamlIlBindingPathHelper+XamlIlBindingPathNode:
System.NullReferenceException: Object reference not set to an instance of an object.
at Mono.Cecil.ImportGenericContext.TypeParameter(String type, Int32 position)
at Mono.Cecil.DefaultMetadataImporter.ImportTypeSpecification(TypeReference type, ImportGenericContext context)
at Mono.Cecil.DefaultMetadataImporter.ImportType(TypeReference type, ImportGenericContext context)
at Mono.Cecil.DefaultMetadataImporter.ImportTypeSpecification(TypeReference type, ImportGenericContext context)
at Mono.Cecil.DefaultMetadataImporter.ImportType(TypeReference type, ImportGenericContext context)
at Mono.Cecil.DefaultMetadataImporter.ImportReference(TypeReference type, IGenericParameterProvider context)
at XamlX.TypeSystem.CecilTypeSystem.CecilEmitter.Import(TypeReference r)
at XamlX.TypeSystem.CecilTypeSystem.CecilEmitter.Import(FieldReference f)
at XamlX.TypeSystem.CecilTypeSystem.CecilEmitter.Emit(OpCode code, IXamlField field)
at XamlX.IL.XamlIlEmitterExtensions.Ldsfld(IXamlILEmitter emitter, IXamlField field)
at Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.XamlIlBindingPathHelper.XamlIlAvaloniaPropertyProperty PathElementNode.Emit(XamlEmitContext`2 context, IXamlILEmitter codeGen)
at Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.XamlIlBindingPathHelper.XamlIlBindingPathNode.Emit(XamlEmitContext`2 context, IXamlILEmitter codeGen)
at XamlX.Emit.XamlEmitContext`2.EmitNodeCore(IXamlAstNode value, TBackendEmitter codeGen, Boolean& foundEmitter)
at XamlX.Emit.XamlEmitContextWithLocals`2.EmitNodeCore(IXamlAstNode value, TBackendEmitter codeGen, Boolean& foundEmitter)
at XamlX.IL.ILEmitContext.EmitNodeCore(IXamlAstNode value, IXamlILEmitter codeGen, Boolean& foundEmitter)
at XamlX.Emit.XamlEmitContext`2.EmitNode(IXamlAstNode value, TBackendEmitter codeGen)
To Reproduce
-
Create a project from
Avalonia.Mvvm
template (calledDemo
in my case) -
Change
MainWindow
to extendReactiveWindow
:using Avalonia.ReactiveUI; using Demo.ViewModels; namespace Demo.Views { public partial class MainWindow : ReactiveWindow<MainWindowViewModel> { public MainWindow() { InitializeComponent(); } } }
-
Change
MainWindow.xaml
to use compiled bindings and bind to generic propertyTViewModel ReactiveWindow<TViewModel>.ViewModel
:<Window xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="using:Demo.ViewModels" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Demo.Views.MainWindow" x:CompileBindings="True" x:DataType="vm:MainWindowViewModel" x:Name="root" Icon="/Assets/avalonia-logo.ico" Title="Demo"> <Design.DataContext> <vm:MainWindowViewModel/> </Design.DataContext> <TextBlock Text="{Binding #root.ViewModel.Greeting}" HorizontalAlignment="Center" VerticalAlignment="Center"/> </Window>
Expected behavior
The binding should work.
Desktop (please complete the following information):
- OS: Windows 11
- Version: 0.10.17 and latest nightly
Why would you need Binding to named root here. Doesn't ReactiveWindow set any DataContext?
Yeah, normally this won't be a problem with data context. That's why it wasn't noticed before. But sometimes you would need to bind to the generic property anyway.
@maxkatz6 I agree that this would be very cool if that would work with CompiledBindings. I just wonder how the compiler should know the type of the generic item. 🤷♂️
Actually I have another question.
As per documentation
Compiled bindings have some known limitations: Compiled bindings cannot be used to bind to named elements
Then why it should work in this case?
@rabbitism IIRC it was a limitation inside of the control templates. With latest master it should have been fixed.
Why would you need Binding to named root here. Doesn't ReactiveWindow set any DataContext?
It does, and I was trying to bind to DataContext
, but compiled bindings doesn't seem to look at x:DataType
when binding through named reference, e.g. for {Binding #root.DataContext.Greeting}
:
Avalonia error XAMLIL: Unable to resolve property or method of name 'Greeting' on type 'System.Object'.
I just wonder how the compiler should know the type of the generic item.
In this case it shouldn't even be generic, as the compiler has a generic type instance with all arguments known (ReactiveWindow<MainWindowViewModel>
), but for some reason it's probably using the generic type definition (i.e. ReactiveWindow<TViewModel>
), so all generic arguments are null
.
but compiled bindings doesn't seem to look at x:DataType when binding through named reference, e.g. for {Binding #root.DataContext.Greeting}:
Yeah, that's expected since DataContext is an object
type class member. Compiler doesn't understand if it's current data context or some other control.
But you can cast it:
{Binding #root.DataContext.(vm:MyViewModel.Greeting)}
which will probably fail again since you need to cast to the generic view model/
The view model is not generic, only the property definition, so {Binding #root.DataContext.(vm:MainWindowViewModel.Greeting)}
should work, but it throws a different NRE:
Avalonia error XAMLIL: Internal compiler error while transforming node XamlX.Ast.XamlAstConstructableObjectNode:
Avalonia error XAMLIL: System.NullReferenceException: Object reference not set to an instance of an object.
Avalonia error XAMLIL: at Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.XamlIlAvaloniaPropertyHelper.GetAvaloniaPropertyType(IXamlField field, AvaloniaXamlIlWellKnownTypes types, IXamlLineInfo lineInfo)
Avalonia error XAMLIL: at Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.XamlIlBindingPathHelper.TransformBindingPath(AstTransformationContext context, IXamlLineInfo lineInfo, Func`1 startTypeResolver, IXamlType selfType, IEnumerable`1 bindingExpression)
Avalonia error XAMLIL: at Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.XamlIlBindingPathHelper.UpdateCompiledBindingExtension(AstTransformationContext context, XamlAstConstructableObjectNode binding, Func`1 startTypeResolver, IXamlType selfType)
Avalonia error XAMLIL: at Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers.AvaloniaXamlIlBindingPathTransformer.Transform(AstTransformationContext context, IXamlAstNode node)
Avalonia error XAMLIL: at XamlX.Transform.AstTransformationContext.Visitor.Visit(IXamlAstNode node) Line 18, position 16.
I also tried {Binding #root.DataContext.(vm|MainWindowViewModel.Greeting)}
(i.e. |
instead of :
) and the error is Avalonia.Data.Core.ExpressionParseException: Invalid attached property name.
.
@jp2masa you TextBlock should already inherit the parents DataContext. So just {Binding Greeting}
should work normally. No need for such a complex binding path.
@jp2masa your best bet is to provide a sample on Github and link it here.
Also if you are new to MVVM, we have a brand new tutorial here: https://github.com/AvaloniaUI/Avalonia.Samples/tree/main/src%2FAvalonia.Samples%2FMVVM%2FBasicMvvmSample
I know it doesn't make sense, but it's a simple repro I created for this bug. In my real project I have to bind to the root user control data context from a data template.
Double checked and this issue is fixed by https://github.com/kekekeks/XamlX/pull/114