unoplatform / uno

Build Mobile, Desktop and WebAssembly apps with C# and XAML. Today. Open source and professionally supported.
https://platform.uno
Apache License 2.0
8.69k stars 700 forks source link

WASM issue with `ToString` on `CustomAttributeData` from `System.Reflection` #10660

Open michael-hawker opened 1 year ago

michael-hawker commented 1 year ago

Current behavior

Have been attempting to read custom assembly metadata attributes in WASM for Toolkit Labs. I think it may be working in the stand-alone Uno project that I built, but need to investigate further.

However, when I was setting up my sample project I had a lot of trouble with CustomAttributeData for what was a straight-forward scenario with UWP to bind and dump with ToString.

My initial attempt resulted in a compile error:

  1. Attempt 1 - WASM build error:
1> C:\Users\source\repos\UnoTestWASMAssemblyMetadata\UnoTestWASMAssemblyMetadata\UnoTestWASMAssemblyMetadata.Wasm\Uno.UI.SourceGenerators\Uno.UI.SourceGenerators.XamlGenerator.XamlCodeGenerator\MainPage_6d2410ab684b9af9dea603e399cb27c8.cs(200,212,200,213): error CS1001: Identifier expected

Couldn't seem to load intermediary file to see what it was complaining about in more detail.

And then a runtime error trying to do the ToString in code-behind:

  1. Attempt 2 & 3 - InvalidCastException at Runtime (WASM)
Runtime Stack Trace from Edge

VM108:1
fail: Uno.UI.Dispatching.CoreDispatcher[0] Dispatcher unhandled exception System.InvalidOperationException: Failed to load UnoTestWASMAssemblyMetadata.MainPage: System.InvalidCastException: Specified cast is not valid. at System.Reflection.CustomAttributeTypedArgument.ToString(Boolean typed) in D:\a\Uno.DotnetRuntime.WebAssembly\Uno.DotnetRuntime.WebAssembly\runtime\src\libraries\System.Private.CoreLib\src\System\Reflection\CustomAttributeTypedArgument.cs:line 60 at System.Reflection.CustomAttributeTypedArgument.ToString() in D:\a\Uno.DotnetRuntime.WebAssembly\Uno.DotnetRuntime.WebAssembly\runtime\src\libraries\System.Private.CoreLib\src\System\Reflection\CustomAttributeTypedArgument.cs:line 36 at System.Reflection.CustomAttributeData.ToString() in D:\a\Uno.DotnetRuntime.WebAssembly\Uno.DotnetRuntime.WebAssembly\runtime\src\libraries\System.Private.CoreLib\src\System\Reflection\CustomAttributeData.cs:line 66 at UnoTestWASMAssemblyMetadata.MainPage.<>c.<.ctor>b__4_0(CustomAttributeData i) in C:\Users\mhawker\source\repos\UnoTestWASMAssemblyMetadata\UnoTestWASMAssemblyMetadata\UnoTestWASMAssemblyMetadata.Shared\MainPage.xaml.cs:line 30 at System.Linq.Enumerable.SelectIListIterator`2[[System.Reflection.CustomAttributeData, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.String, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext() in D:\a\Uno.DotnetRuntime.WebAssembly\Uno.DotnetRuntime.WebAssembly\runtime\src\libraries\System.Linq\src\System\Linq\Select.cs:line 280 at System.Collections.Generic.List`1[[System.String, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]..ctor(IEnumerable`1 collection) in D:\a\Uno.DotnetRuntime.WebAssembly\Uno.DotnetRuntime.WebAssembly\runtime\src\libraries\System.Private.CoreLib\src\System\Collections\Generic\List.cs:line 85 at System.Linq.Enumerable.ToList[String](IEnumerable`1 source) in D:\a\Uno.DotnetRuntime.WebAssembly\Uno.DotnetRuntime.WebAssembly\runtime\src\libraries\System.Linq\src\System\Linq\ToCollection.cs:line 29 at UnoTestWASMAssemblyMetadata.MainPage..ctor() in C:\Users\mhawker\source\repos\UnoTestWASMAssemblyMetadata\UnoTestWASMAssemblyMetadata\UnoTestWASMAssemblyMetadata.Shared\MainPage.xaml.cs:line 30 at UnoTestWASMAssemblyMetadata.Wasm.MetadataBuilder_644.CreateInstance() in C:\Users\mhawker\source\repos\UnoTestWASMAssemblyMetadata\UnoTestWASMAssemblyMetadata\UnoTestWASMAssemblyMetadata.Wasm\Uno.UI.SourceGenerators\Uno.UI.SourceGenerators.BindableTypeProviders.BindableTypeProvidersSourceGenerator\BindableMetadata.g.cs:line 23434 at Windows.UI.Xaml.Controls.Frame.CreatePageInstance(Type sourcePageType) in /home/vsts/work/1/s/src/Uno.UI/UI/Xaml/Controls/Frame/Frame.cs:line 454 at Windows.UI.Xaml.PagePool.DequeuePage(Type pageType) in /home/vsts/work/1/s/src/Uno.UI/UI/Xaml/PagePool.cs:line 69 at Windows.UI.Xaml.Controls.Frame.CreatePageInstanceCached(Type sourcePageType) in /home/vsts/work/1/s/src/Uno.UI/UI/Xaml/Controls/Frame/Frame.cs:line 444 at Windows.UI.Xaml.Controls.Frame.EnsurePageInitialized(PageStackEntry entry) in /home/vsts/work/1/s/src/Uno.UI/UI/Xaml/Controls/Frame/Frame.cs:line 435 at Windows.UI.Xaml.Controls.Frame.InnerNavigate(PageStackEntry entry, NavigationMode mode) in /home/vsts/work/1/s/src/Uno.UI/UI/Xaml/Controls/Frame/Frame.cs:line 283 at UnoTestWASMAssemblyMetadata.App.OnNavigationFailed(Object sender, NavigationFailedEventArgs e) in C:\Users\mhawker\source\repos\UnoTestWASMAssemblyMetadata\UnoTestWASMAssemblyMetadata\UnoTestWASMAssemblyMetadata.Shared\App.xaml.cs:line 107 at Windows.UI.Xaml.Controls.Frame.InnerNavigate(PageStackEntry entry, NavigationMode mode) in /home/vsts/work/1/s/src/Uno.UI/UI/Xaml/Controls/Frame/Frame.cs:line 300 at Windows.UI.Xaml.Controls.Frame.Navigate(Type sourcePageType, Object parameter, NavigationTransitionInfo infoOverride) in /home/vsts/work/1/s/src/Uno.UI/UI/Xaml/Controls/Frame/Frame.cs:line 276 at Windows.UI.Xaml.Controls.Frame.Navigate(Type sourcePageType, Object parameter) in /home/vsts/work/1/s/src/Uno.UI/UI/Xaml/Controls/Frame/Frame.cs:line 271 at UnoTestWASMAssemblyMetadata.App.OnLaunched(LaunchActivatedEventArgs args) in C:\Users\mhawker\source\repos\UnoTestWASMAssemblyMetadata\UnoTestWASMAssemblyMetadata\UnoTestWASMAssemblyMetadata.Shared\App.xaml.cs:line 93 at Windows.UI.Xaml.Application.Initialize() in /home/vsts/work/1/s/src/Uno.UI/UI/Xaml/Application.wasm.cs:line 130 at Uno.UI.Dispatching.CoreDispatcher.InvokeOperationSafe(UIAsyncOperation operation) in /home/vsts/work/1/s/src/Uno.UI.Dispatching/Core/CoreDispatcher.cs:line 337
  | eval | @ | VM108:1 -- | -- | -- | --

Think pointing to here: https://github.com/dotnet/runtimelab/blob/04b092003db5ba207d4fa3f3becb7f01828bf16c/src/libraries/System.Private.CoreLib/src/System/Reflection/CustomAttributeTypedArgument.cs#L60

Expected behavior

Running on UWP:

image

How to reproduce it (as minimally and precisely as possible)

  1. Create a new Xamarin | UWP Uno Project App in VS 2022
  2. Modify MainPage.xaml.cs:
        // Attempt 1
        public IList<CustomAttributeData> AssemblyMetadata { get; set; }

        // Attempt 2+
        ////public IList<string> AssemblyMetadata { get; set; }

        public MainPage()
        {
            // Note all commented out code worked fine on UWP.

            // Attempt 1: Initially tried this directly:
            AssemblyMetadata = Assembly.GetExecutingAssembly().GetCustomAttributesData();

            // Attempt 2: Then this, but it didn't work (InvalidCastException)
            //// AssemblyMetadata = Assembly.GetExecutingAssembly().GetCustomAttributesData().Select(i => i.ToString()).ToList();

            // Attempt 3
            /*List<string> tmp = new();

            foreach (var metadata in Assembly.GetExecutingAssembly().GetCustomAttributesData())
            {
                tmp.Add(metadata.ToString()); // Failing Line in ToString Call
            }

            AssemblyMetadata = tmp;*/

            // Attempt 4 - Avoiding ToString on the type and using other values works fine
            /*AssemblyMetadata = Assembly.GetExecutingAssembly().GetCustomAttributesData().Select(i => i.AttributeType.Name).ToList();*/

            this.InitializeComponent();
        }
  1. Add to MainPage.xaml
<StackPanel Margin="32" Spacing="8">
        <ItemsControl ItemsSource="{x:Bind AssemblyMetadata}">
            <ItemsControl.ItemTemplate>
                <!--  Attempt 1 (worked fine on UWP)  -->
                <DataTemplate x:DataType="ref:CustomAttributeData">
                    <TextBlock Text="{x:Bind (ref:CustomAttributeData).ToString()}" />
                </DataTemplate>
                <!--  Attempt 2+  -->
                <!--<DataTemplate>
                    <TextBlock Text="{Binding}" />
                </DataTemplate>-->
            </ItemsControl.ItemTemplate>
        </ItemsControl>
        <TextBlock>
            <Run Text="Attribute Count:" />
            <Run Text="{x:Bind AssemblyMetadata.Count}" />
        </TextBlock>
    </StackPanel>

Attached project zip of all scenarios:

UnoTestWASMAssemblyMetadata.zip

Workaround

Not sure how to get full information contained within the type as easily as was provided with ToString, mind you this is a more contrived scenario, as in our case we're getting a specific attribute (though in our other project we don't see that listed, thus why I was building this example).

Works on UWP/WinUI

Yes

Environment

Uno.UI / Uno.UI.WebAssembly / Uno.UI.Skia

NuGet package version(s)

Uno.UI.WebAssembly 4.6.19

Affected platforms

WebAssembly

IDE

Visual Studio 2022

IDE version

17.4.0

Relevant plugins

No response

Anything else we need to know?

Not sure if another edge case of #7893? Though the ToString seems to be involved in the later attempts. Maybe it's two slightly different issues underneath?

Will go back to Labs and try and compare to see why I'm not seeing the same number of attributes as I did in the sample here at least, so this has given me some insight at least even if it did uncover other issues that I don't think may directly effect us at the moment.

jeromelaban commented 1 year ago

This looks like there are two separate issues here.

First, it seems that adding a function invocation to the pathless cast is not working well, but even when doing this:

<TextBlock DataContext="{x:Bind (ref:CustomAttributeData))"
                   Text="{Binding}" />

I'm getting:

System.InvalidCastException: Unable to cast object of type 'Microsoft.CodeAnalysis.CSharp.Syntax.AssignmentExpressionSyntax' to type 'Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax'

I'm not able to repro in Unit Tests, so there must be something else that breaks that particular scenario.

That being said, you're not forced to use x:Bind here, and a simple:

<TextBlock Text="{Binding}" />

Will be enough as a workaround.

michael-hawker commented 1 year ago

Thanks @jeromelaban, I didn't try just using {Binding} but with the original object type. That does output the ToString result properly! 🎉 It is weird that calling the ToString method directly in code-behind causes an exception though? I don't know how it'd take a different path.

Still investigating in Labs what might be going on there with our attributes, but our generation of them is a bit different. This info will help that investigation though. Thanks!

jeromelaban commented 1 year ago

Great! As for the error with CustomAttribute, I suspect this is a runtime issue that may (or may not) be fixed with .NET 7 (bootstrapper 7.0.x). Could be worth a try.

michael-hawker commented 1 year ago

Ah, figured out my base issue we had in Labs in regard to where I was retrieving the Metadata (which existed and was valid with our new generation system), but not where I was setting the property/evaluating the bindings... I was basically hitting the #2666 #2872 issues again.

So at least I've got everything I need working for Labs at the moment!