Avalonia icon indicating copy to clipboard operation
Avalonia copied to clipboard

[Compiled Bindings] NRE when trying to bind to a generic property

Open jp2masa opened this issue 1 year ago • 11 comments

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

  1. Create a project from Avalonia.Mvvm template (called Demo in my case)

  2. Change MainWindow to extend ReactiveWindow:

    using Avalonia.ReactiveUI;
    
    using Demo.ViewModels;
    
    namespace Demo.Views
    {
        public partial class MainWindow : ReactiveWindow<MainWindowViewModel>
        {
            public MainWindow()
            {
                InitializeComponent();
            }
        }
    }
    
  3. Change MainWindow.xaml to use compiled bindings and bind to generic property TViewModel 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

jp2masa avatar Aug 06 '22 05:08 jp2masa

Why would you need Binding to named root here. Doesn't ReactiveWindow set any DataContext?

timunie avatar Aug 06 '22 08:08 timunie

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 avatar Aug 06 '22 08:08 maxkatz6

@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. 🤷‍♂️

timunie avatar Aug 06 '22 08:08 timunie

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 avatar Aug 06 '22 10:08 rabbitism

@rabbitism IIRC it was a limitation inside of the control templates. With latest master it should have been fixed.

maxkatz6 avatar Aug 06 '22 16:08 maxkatz6

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.

jp2masa avatar Aug 06 '22 20:08 jp2masa

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/

maxkatz6 avatar Aug 06 '22 20:08 maxkatz6

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 avatar Aug 06 '22 21:08 jp2masa

@jp2masa you TextBlock should already inherit the parents DataContext. So just {Binding Greeting} should work normally. No need for such a complex binding path.

timunie avatar Aug 08 '22 06:08 timunie

@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

timunie avatar Aug 08 '22 06:08 timunie

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.

jp2masa avatar Aug 08 '22 07:08 jp2masa

Double checked and this issue is fixed by https://github.com/kekekeks/XamlX/pull/114

maxkatz6 avatar Apr 17 '24 23:04 maxkatz6