chore: Preserve generated *ViewModel constructors, properties - #2966
Conversation
e43d9e2 to
0040fcd
Compare
Context: #2966 Context: unoplatform/uno.chefs#1709 While running the uno.chefs app on macOS under NativeAOT: dotnet publish -c Release -r osx-x64 -f net10.0-desktop -p:TargetFrameworkOverride=net10.0-desktop -bl \ Chefs/Chefs.csproj \ -p:SelfContained=true -p:PublishAot=true -p:IsAotCompatible=true -p:UseSkiaRendering=true \ -p:IlcGenerateMapFile=true -p:IlcGenerateMstatFile=true -p:IlcGenerateDgmlFile=true \ -p:EmitCompilerGeneratedFiles=true -p:CompilerGeneratedFilesOutputPath=`pwd`/_gen Chefs/bin/Release/net10.0-desktop/osx-x64/publish/Chefs (along with a change from unoplatform/uno.chefs#1709 to `Program.cs` to call `App.InitializeLogging()`…) Console output would contain the following error: fail: Uno.UI.DataBinding.BindingPropertyHelper[0] The [CurrentIndex] property getter does not exist on type [Chefs.Business.Models.BindableIntIteratorViewModel] fail: Uno.UI.DataBinding.BindingPropertyHelper[0] The property setter for [CurrentIndex] does not exist on [Chefs.Business.Models.BindableIntIteratorViewModel] fail: Uno.UI.DataBinding.BindingPropertyHelper[0] The [Value] property getter does not exist on type [Chefs.Business.Models.BindableIntIteratorViewModel] PR #2966 had a similar set of messages, and the general fix is to use `[DynamicallyAccessedMembers]` so that NativeAOT preserves property metadata. The problem *here* is that there is no good place to put `[DynamicallyAccessedMembers]` that will preserve the ViewModel types generated by Uno.Extensions.Reactive.Generator: namespace Chefs.Business.Models { [GeneratedCodeAttribute("ViewModelGenerator_2", "2")] [Bindable(typeof(IntIterator))] public partial class BindableIntIteratorViewModel : global::Uno.Extensions.Reactive.Bindings.Bindable<Chefs.Business.Models.IntIterator> { public IntIterator Value {get => …; set => …;} public int CurrentIndex {get => …; set => …;} } } namespace Chefs.Presentation { [GeneratedCodeAttribute("ViewModelGenTool_3", "3")] [Bindable(typeof(global::Chefs.Presentation.WelcomeModel))] public partial class WelcomeViewModel { protected WelcomeViewModel(global::Chefs.Presentation.WelcomeModel model) { // … Pages ??= new Chefs.Business.Models.BindableIntIteratorViewModel(…); // … } public Chefs.Business.Models.BindableIntIteratorViewModel Pages { get; private set; } } } Nothing tells NativeAOT to preserve the `BindableIntIteratorViewModel` properties, so they're "removed" from Reflection metadata. Update `BindableFromFeedProperty.cs` to use `[DynamicDependency]` on the `WelcomeViewModel.Pages` property accessor: namespace Chefs.Presentation { public partial class WelcomeViewModel { public Chefs.Business.Models.BindableIntIteratorViewModel Pages { [DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(Chefs.Business.Models.BindableIntIteratorViewModel))] get; private set; } } } This change informs NativeAOT that if the `WelcomeViewModel.Pages` property getter is preserved, then the public properties on `BindableIntIteratorViewModel` should also be made accessible via Reflection. This fixes the original message: fail: Uno.UI.DataBinding.BindingPropertyHelper[0] The [CurrentIndex] property getter does not exist on type [Chefs.Business.Models.BindableIntIteratorViewModel] …only to replace it with this *different* set of failure messages: fail: Uno.UI.DataBinding.BindingPropertyHelper[0] The [CanMovePrevious] property getter does not exist on type [Chefs.Business.Models.IntIterator] fail: Uno.UI.DataBinding.BindingPropertyHelper[0] The [CanMovePrevious] property getter does not exist on type [Chefs.Business.Models.IntIterator] fail: Uno.UI.DataBinding.BindingPropertyHelper[0] The [CanMoveNext] property getter does not exist on type [Chefs.Business.Models.IntIterator] fail: Uno.UI.DataBinding.BindingPropertyHelper[0] The [CanMoveNext] property getter does not exist on type [Chefs.Business.Models.IntIterator] Address this new set of messages by updating `Bindable<T>` to partial class Bindable< [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties)] T > : IBindable { } As `BindableIntIteratorViewModel` inherits from `Bindable<IntIterator>` this causes the public properties on `IntIterator` to be preserved, fixing the previous set of failure messages.
Context: unoplatform/uno.chefs#1712 Context: #21920 Context: unoplatform/uno.extensions#2966 Context: unoplatform/uno.extensions#2969 Context: https://discord.com/channels/732297728826277939/732297837953679412/1444909702512119899 While running the uno.chefs app on macOS under NativeAOT: dotnet publish -c Release -r osx-x64 -f net10.0-desktop -p:TargetFrameworkOverride=net10.0-desktop -bl \ Chefs/Chefs.csproj \ -p:SelfContained=true -p:PublishAot=true -p:IsAotCompatible=true -p:UseSkiaRendering=true \ -p:IlcGenerateMapFile=true -p:IlcGenerateMstatFile=true -p:IlcGenerateDgmlFile=true \ -p:EmitCompilerGeneratedFiles=true -p:CompilerGeneratedFilesOutputPath=`pwd`/_gen Chefs/bin/Release/net10.0-desktop/osx-x64/publish/Chefs (along with a change from unoplatform/uno.chefs#1712) Console output would contain the following errors: fail: Uno.UI.Dispatching.NativeDispatcher[0] The [TabNavigation] property getter does not exist on type [Microsoft.UI.Xaml.Controls.FlipView] fail: Uno.UI.Dispatching.NativeDispatcher[0] The [Y] property getter does not exist on type [Windows.Foundation.Point] fail: Uno.UI.Dispatching.NativeDispatcher[0] The [X] property getter does not exist on type [Windows.Foundation.Point] At this point, a description of the relation between NativeAOT and Reflection would be useful. Parts of System.Reflection can be used. Which parts *can* be used, and how to use them, is oddly *not* well specified in the [Native AOT deployment][0] documentation. What doesn't work is outlined in the [limitations][1] section: > * No dynamic loading, for example, `Assembly.LoadFile`. > * No run-time code generation, for example, `System.Reflection.Emit`. What *always* appears to work is: * `Type.GetType()` returns a `Type` instance. What ***can*** work is: * `Type.GetType(string)`, *so long as* an assembly-qualified name is used as a *string constant* // bad string name = GetSomeTypeName(); Type type = Type.GetType(name); // good Type type = Type.GetType("My.Example.TypeName, ExampleAssembly"); * `Type.GetMethods()`, but see fine print below. * `Type.GetProperties()`, but see fine print below. * `MethodInfo.Invoke()` * … **The Fine Print**: in order for many Reflection-based APIs such as `Type.GetMethods()` to work, the program must contain "reflection metadata." We can now describe, briefly, what NativeAOT does: 1. Accept IL/assemblies 2. Internally trims the IL, optimizes them, etc. 3. Generates a native binary containing two separate bits of data: * Native machine code for execution * "reflection metadata" There is no facility to separately dump reflection metadata. Programs can control what is contained within reflection metadata by using string constants with Reflection APIs, for example: typeof(SomeKnownType).GetMethod("ConstantName") will ensure that reflection metadata contains `SomeKnownType.ConstantName()`, and `ConstantName()` will be returned from `typeof(SomeKnownType).GetMethods()` and can be invoked with `MethodInfo.Invoke()`. If you *don't* use `typeof(SomeKnownType)` or string constants with `Type.GetType(string)` or `type.GetMethod("constant")`, then you need to "suggest" that some pieces of information be added to reflection metadata. The two predominant ways to do this are via the custom attributes: * [`DynamicallyAccessedMembersAttribute`][2] * [`DynamicDependencyAttribute`][3] From the Uno perspective, enter `BindingPropertyHelper`: *everything* is based on Reflection! Fortunately NativeAOT "supports" Reflection! *Unfortunately*, we now need to convince NativeAOT to store the reflection metadata Uno requires in order to work! Various other PRs have dealt with some of this, such as #21920. Returning to the original failure messages, e.g. fail: Uno.UI.Dispatching.NativeDispatcher[0] The [TabNavigation] property getter does not exist on type [Microsoft.UI.Xaml.Controls.FlipView] During the app build, `src/SourceGenerators/Uno.UI.SourceGenerators` generates a a `BindableMetadata.g.cs` file which will mention `[Bindable]` types, e.g. for `Microsoft.UI.Xaml.Controls.FlipView`: /// <summary> /// Builder for Microsoft.UI.Xaml.Controls.FlipView /// </summary> [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute] static class MetadataBuilder_239 { internal static global::Uno.UI.DataBinding.IBindableType Build(global::Uno.UI.DataBinding.BindableType parent) { var bindableType = parent ?? new global::Uno.UI.DataBinding.BindableType(151, typeof(global::Microsoft.UI.Xaml.Controls.FlipView)); // … } private static object CreateInstance() => new global::Microsoft.UI.Xaml.Controls.FlipView(); } `BindableMetadata.g.cs` was needed in the ye olde Xamarin world order to tell *its* linker to e.g. "preserve the `FlipView` default ctor!" *Now*, we can build upon this infrastructure and update `BindableType` to request that Reflection information regarding properties, fields, and constructors be retained. We can do this by updating the `BindableType` constructor: partial class BindableType { internal const DynamicallyAccessedMemberTypes TypeRequirements = DynamicallyAccessedMemberTypes.PublicProperties | …; public BindableType(int estimatedPropertySize, [DynamicallyAccessedMembers(TypeRequirements)] Type sourceType ) => … } *Because* we have `BindableMetadata.g.cs` *and* because it provides `typeof(FlipView)`, this directs NativeAOT to make property and other reflection metadata available to the app, *fixing* the error messages. Expand this addition of `[DynamicallyAccessedMembers]` to also include `BindableProperty` and related types. [0]: https://learn.microsoft.comdotnet/core/deploying/native-aot [1]: https://learn.microsoft.comdotnet/core/deploying/native-aot/#limitations-of-native-aot-deployment [2]: https://learn.microsoft.comdotnet/api/system.diagnostics.codeanalysis.dynamicallyaccessedmembersattribute?view=net-10.0 [3]: https://learn.microsoft.comdotnet/api/system.diagnostics.codeanalysis.dynamicdependencyattribute?view=net-10.0
Context: #2966 While running the uno.chefs app on macOS under NativeAOT: dotnet publish -c Release -r osx-x64 -f net10.0-desktop -p:TargetFrameworkOverride=net10.0-desktop -bl \ Chefs/Chefs.csproj \ -p:SelfContained=true -p:PublishAot=true -p:IsAotCompatible=true -p:UseSkiaRendering=true \ -p:IlcGenerateMapFile=true -p:IlcGenerateMstatFile=true -p:IlcGenerateDgmlFile=true \ -p:EmitCompilerGeneratedFiles=true -p:CompilerGeneratedFilesOutputPath=`pwd`/_gen Chefs/bin/Release/net10.0-desktop/osx-x64/publish/Chefs Console output would contain the following error: fail: Uno.UI.DataBinding.BindingPropertyHelper[0] The [CurrentIndex] property getter does not exist on type [Chefs.Business.Models.BindableIntIteratorViewModel] fail: Uno.UI.DataBinding.BindingPropertyHelper[0] The property setter for [CurrentIndex] does not exist on [Chefs.Business.Models.BindableIntIteratorViewModel] fail: Uno.UI.DataBinding.BindingPropertyHelper[0] The [Value] property getter does not exist on type [Chefs.Business.Models.BindableIntIteratorViewModel] PR #2966 had a similar set of messages, and the general fix is to use `[DynamicallyAccessedMembers]` so that NativeAOT preserves property metadata. The problem *here* is that there is no good place to put `[DynamicallyAccessedMembers]` that will preserve the ViewModel types generated by Uno.Extensions.Reactive.Generator: namespace Chefs.Business.Models { [GeneratedCodeAttribute("ViewModelGenerator_2", "2")] [Bindable(typeof(IntIterator))] public partial class BindableIntIteratorViewModel : global::Uno.Extensions.Reactive.Bindings.Bindable<Chefs.Business.Models.IntIterator> { public IntIterator Value {get => …; set => …;} public int CurrentIndex {get => …; set => …;} } } namespace Chefs.Presentation { [GeneratedCodeAttribute("ViewModelGenTool_3", "3")] [Bindable(typeof(global::Chefs.Presentation.WelcomeModel))] public partial class WelcomeViewModel { protected WelcomeViewModel(global::Chefs.Presentation.WelcomeModel model) { // … Pages ??= new Chefs.Business.Models.BindableIntIteratorViewModel(…); // … } public Chefs.Business.Models.BindableIntIteratorViewModel Pages { get; private set; } } } Nothing tells NativeAOT to preserve the `BindableIntIteratorViewModel` properties, so they're "removed" from Reflection metadata. Update `BindableFromFeedProperty.cs` to use `[DynamicDependency]` on the `WelcomeViewModel.Pages` property accessor: namespace Chefs.Presentation { public partial class WelcomeViewModel { public Chefs.Business.Models.BindableIntIteratorViewModel Pages { [DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(Chefs.Business.Models.BindableIntIteratorViewModel))] get; private set; } } } This change informs NativeAOT that if the `WelcomeViewModel.Pages` property getter is preserved, then the public properties on `BindableIntIteratorViewModel` should also be made accessible via Reflection. This fixes the original message: fail: Uno.UI.DataBinding.BindingPropertyHelper[0] The [CurrentIndex] property getter does not exist on type [Chefs.Business.Models.BindableIntIteratorViewModel] …only to replace it with this *different* set of failure messages: fail: Uno.UI.DataBinding.BindingPropertyHelper[0] The [CanMovePrevious] property getter does not exist on type [Chefs.Business.Models.IntIterator] fail: Uno.UI.DataBinding.BindingPropertyHelper[0] The [CanMovePrevious] property getter does not exist on type [Chefs.Business.Models.IntIterator] fail: Uno.UI.DataBinding.BindingPropertyHelper[0] The [CanMoveNext] property getter does not exist on type [Chefs.Business.Models.IntIterator] fail: Uno.UI.DataBinding.BindingPropertyHelper[0] The [CanMoveNext] property getter does not exist on type [Chefs.Business.Models.IntIterator] Address this new set of messages by updating `Bindable<T>` to partial class Bindable< [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T > : IBindable { } As `BindableIntIteratorViewModel` inherits from `Bindable<IntIterator>` this causes the public properties on `IntIterator` to be preserved, fixing the previous set of failure messages. Adding `[DynamicallyAccessedMembers]` to `Bindable<T>` breaks `BindableEnumerable<…>`: BindableEnumerable.cs(22,23): error IL2091: 'T' generic argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicProperties' in 'Uno.Extensions.Reactive.Bindings.Bindable<T>'. The generic parameter 'TCollection' of 'Uno.Extensions.Reactive.Bindings.BindableEnumerable<TCollection, TItem, TBindableItem>' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. Annotate the `TCollection` type parameter accordingly to fix the error.
0040fcd to
c0d699a
Compare
Context: unoplatform/uno.chefs#1712 Context: #21920 Context: unoplatform/uno.extensions#2966 Context: unoplatform/uno.extensions#2969 Context: https://discord.com/channels/732297728826277939/732297837953679412/1444909702512119899 While running the uno.chefs app on macOS under NativeAOT: dotnet publish -c Release -r osx-x64 -f net10.0-desktop -p:TargetFrameworkOverride=net10.0-desktop -bl \ Chefs/Chefs.csproj \ -p:SelfContained=true -p:PublishAot=true -p:IsAotCompatible=true -p:UseSkiaRendering=true \ -p:IlcGenerateMapFile=true -p:IlcGenerateMstatFile=true -p:IlcGenerateDgmlFile=true \ -p:EmitCompilerGeneratedFiles=true -p:CompilerGeneratedFilesOutputPath=`pwd`/_gen Chefs/bin/Release/net10.0-desktop/osx-x64/publish/Chefs (along with a change from unoplatform/uno.chefs#1712) Console output would contain the following errors: fail: Uno.UI.Dispatching.NativeDispatcher[0] The [TabNavigation] property getter does not exist on type [Microsoft.UI.Xaml.Controls.FlipView] fail: Uno.UI.Dispatching.NativeDispatcher[0] The [Y] property getter does not exist on type [Windows.Foundation.Point] fail: Uno.UI.Dispatching.NativeDispatcher[0] The [X] property getter does not exist on type [Windows.Foundation.Point] At this point, a description of the relation between NativeAOT and Reflection would be useful. Parts of System.Reflection can be used. Which parts *can* be used, and how to use them, is oddly *not* well specified in the [Native AOT deployment][0] documentation. What doesn't work is outlined in the [limitations][1] section: > * No dynamic loading, for example, `Assembly.LoadFile`. > * No run-time code generation, for example, `System.Reflection.Emit`. What *always* appears to work is: * `Type.GetType()` returns a `Type` instance. What ***can*** work is: * `Type.GetType(string)`, *so long as* an assembly-qualified name is used as a *string constant* // bad string name = GetSomeTypeName(); Type type = Type.GetType(name); // good Type type = Type.GetType("My.Example.TypeName, ExampleAssembly"); * `Type.GetMethods()`, but see fine print below. * `Type.GetProperties()`, but see fine print below. * `MethodInfo.Invoke()` * … **The Fine Print**: in order for many Reflection-based APIs such as `Type.GetMethods()` to work, the program must contain "reflection metadata." We can now describe, briefly, what NativeAOT does: 1. Accept IL/assemblies 2. Internally trims the IL, optimizes them, etc. 3. Generates a native binary containing two separate bits of data: * Native machine code for execution * "reflection metadata" There is no facility to separately dump reflection metadata. Programs can control what is contained within reflection metadata by using string constants with Reflection APIs, for example: typeof(SomeKnownType).GetMethod("ConstantName") will ensure that reflection metadata contains `SomeKnownType.ConstantName()`, and `ConstantName()` will be returned from `typeof(SomeKnownType).GetMethods()` and can be invoked with `MethodInfo.Invoke()`. If you *don't* use `typeof(SomeKnownType)` or string constants with `Type.GetType(string)` or `type.GetMethod("constant")`, then you need to "suggest" that some pieces of information be added to reflection metadata. The two predominant ways to do this are via the custom attributes: * [`DynamicallyAccessedMembersAttribute`][2] * [`DynamicDependencyAttribute`][3] From the Uno perspective, enter `BindingPropertyHelper`: *everything* is based on Reflection! Fortunately NativeAOT "supports" Reflection! *Unfortunately*, we now need to convince NativeAOT to store the reflection metadata Uno requires in order to work! Various other PRs have dealt with some of this, such as #21920. Returning to the original failure messages, e.g. fail: Uno.UI.Dispatching.NativeDispatcher[0] The [TabNavigation] property getter does not exist on type [Microsoft.UI.Xaml.Controls.FlipView] During the app build, `src/SourceGenerators/Uno.UI.SourceGenerators` generates a a `BindableMetadata.g.cs` file which will mention `[Bindable]` types, e.g. for `Microsoft.UI.Xaml.Controls.FlipView`: /// <summary> /// Builder for Microsoft.UI.Xaml.Controls.FlipView /// </summary> [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute] static class MetadataBuilder_239 { internal static global::Uno.UI.DataBinding.IBindableType Build(global::Uno.UI.DataBinding.BindableType parent) { var bindableType = parent ?? new global::Uno.UI.DataBinding.BindableType(151, typeof(global::Microsoft.UI.Xaml.Controls.FlipView)); // … } private static object CreateInstance() => new global::Microsoft.UI.Xaml.Controls.FlipView(); } `BindableMetadata.g.cs` was needed in the ye olde Xamarin world order to tell *its* linker to e.g. "preserve the `FlipView` default ctor!" *Now*, we can build upon this infrastructure and update `BindableType` to request that Reflection information regarding properties, fields, and constructors be retained. We can do this by updating the `BindableType` constructor: partial class BindableType { internal const DynamicallyAccessedMemberTypes TypeRequirements = DynamicallyAccessedMemberTypes.PublicProperties | …; public BindableType(int estimatedPropertySize, [DynamicallyAccessedMembers(TypeRequirements)] Type sourceType ) => … } *Because* we have `BindableMetadata.g.cs` *and* because it provides `typeof(FlipView)`, this directs NativeAOT to make property and other reflection metadata available to the app, *fixing* the error messages. Expand this addition of `[DynamicallyAccessedMembers]` to also include `BindableProperty` and related types. [0]: https://learn.microsoft.comdotnet/core/deploying/native-aot [1]: https://learn.microsoft.comdotnet/core/deploying/native-aot/#limitations-of-native-aot-deployment [2]: https://learn.microsoft.comdotnet/api/system.diagnostics.codeanalysis.dynamicallyaccessedmembersattribute?view=net-10.0 [3]: https://learn.microsoft.comdotnet/api/system.diagnostics.codeanalysis.dynamicdependencyattribute?view=net-10.0
Context: unoplatform/uno.chefs#1712 Context: #21920 Context: unoplatform/uno.extensions#2966 Context: unoplatform/uno.extensions#2969 Context: https://discord.com/channels/732297728826277939/732297837953679412/1444909702512119899 While running the uno.chefs app on macOS under NativeAOT: dotnet publish -c Release -r osx-x64 -f net10.0-desktop -p:TargetFrameworkOverride=net10.0-desktop -bl \ Chefs/Chefs.csproj \ -p:SelfContained=true -p:PublishAot=true -p:IsAotCompatible=true -p:UseSkiaRendering=true \ -p:IlcGenerateMapFile=true -p:IlcGenerateMstatFile=true -p:IlcGenerateDgmlFile=true \ -p:EmitCompilerGeneratedFiles=true -p:CompilerGeneratedFilesOutputPath=`pwd`/_gen Chefs/bin/Release/net10.0-desktop/osx-x64/publish/Chefs (along with a change from unoplatform/uno.chefs#1712) Console output would contain the following errors: fail: Uno.UI.Dispatching.NativeDispatcher[0] The [TabNavigation] property getter does not exist on type [Microsoft.UI.Xaml.Controls.FlipView] fail: Uno.UI.Dispatching.NativeDispatcher[0] The [Y] property getter does not exist on type [Windows.Foundation.Point] fail: Uno.UI.Dispatching.NativeDispatcher[0] The [X] property getter does not exist on type [Windows.Foundation.Point] At this point, a description of the relation between NativeAOT and Reflection would be useful. Parts of System.Reflection can be used. Which parts *can* be used, and how to use them, is oddly *not* well specified in the [Native AOT deployment][0] documentation. What doesn't work is outlined in the [limitations][1] section: > * No dynamic loading, for example, `Assembly.LoadFile`. > * No run-time code generation, for example, `System.Reflection.Emit`. What *always* appears to work is: * `Type.GetType()` returns a `Type` instance. What ***can*** work is: * `Type.GetType(string)`, *so long as* an assembly-qualified name is used as a *string constant* // bad string name = GetSomeTypeName(); Type type = Type.GetType(name); // good Type type = Type.GetType("My.Example.TypeName, ExampleAssembly"); * `Type.GetMethods()`, but see fine print below. * `Type.GetProperties()`, but see fine print below. * `MethodInfo.Invoke()` * … **The Fine Print**: in order for many Reflection-based APIs such as `Type.GetMethods()` to work, the program must contain "reflection metadata." We can now describe, briefly, what NativeAOT does: 1. Accept IL/assemblies 2. Internally trims the IL, optimizes them, etc. 3. Generates a native binary containing two separate bits of data: * Native machine code for execution * "reflection metadata" There is no facility to separately dump reflection metadata. Programs can control what is contained within reflection metadata by using string constants with Reflection APIs, for example: typeof(SomeKnownType).GetMethod("ConstantName") will ensure that reflection metadata contains `SomeKnownType.ConstantName()`, and `ConstantName()` will be returned from `typeof(SomeKnownType).GetMethods()` and can be invoked with `MethodInfo.Invoke()`. If you *don't* use `typeof(SomeKnownType)` or string constants with `Type.GetType(string)` or `type.GetMethod("constant")`, then you need to "suggest" that some pieces of information be added to reflection metadata. The two predominant ways to do this are via the custom attributes: * [`DynamicallyAccessedMembersAttribute`][2] * [`DynamicDependencyAttribute`][3] From the Uno perspective, enter `BindingPropertyHelper`: *everything* is based on Reflection! Fortunately NativeAOT "supports" Reflection! *Unfortunately*, we now need to convince NativeAOT to store the reflection metadata Uno requires in order to work! Various other PRs have dealt with some of this, such as #21920. Returning to the original failure messages, e.g. fail: Uno.UI.Dispatching.NativeDispatcher[0] The [TabNavigation] property getter does not exist on type [Microsoft.UI.Xaml.Controls.FlipView] During the app build, `src/SourceGenerators/Uno.UI.SourceGenerators` generates a a `BindableMetadata.g.cs` file which will mention `[Bindable]` types, e.g. for `Microsoft.UI.Xaml.Controls.FlipView`: /// <summary> /// Builder for Microsoft.UI.Xaml.Controls.FlipView /// </summary> [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute] static class MetadataBuilder_239 { internal static global::Uno.UI.DataBinding.IBindableType Build(global::Uno.UI.DataBinding.BindableType parent) { var bindableType = parent ?? new global::Uno.UI.DataBinding.BindableType(151, typeof(global::Microsoft.UI.Xaml.Controls.FlipView)); // … } private static object CreateInstance() => new global::Microsoft.UI.Xaml.Controls.FlipView(); } `BindableMetadata.g.cs` was needed in the ye olde Xamarin world order to tell *its* linker to e.g. "preserve the `FlipView` default ctor!" *Now*, we can build upon this infrastructure and update `BindableType` to request that Reflection information regarding properties, fields, and constructors be retained. We can do this by updating the `BindableType` constructor: partial class BindableType { internal const DynamicallyAccessedMemberTypes TypeRequirements = DynamicallyAccessedMemberTypes.PublicProperties | …; public BindableType(int estimatedPropertySize, [DynamicallyAccessedMembers(TypeRequirements)] Type sourceType ) => … } *Because* we have `BindableMetadata.g.cs` *and* because it provides `typeof(FlipView)`, this directs NativeAOT to make property and other reflection metadata available to the app, *fixing* the error messages. Expand this addition of `[DynamicallyAccessedMembers]` to also include `BindableProperty` and related types. [0]: https://learn.microsoft.comdotnet/core/deploying/native-aot [1]: https://learn.microsoft.comdotnet/core/deploying/native-aot/#limitations-of-native-aot-deployment [2]: https://learn.microsoft.comdotnet/api/system.diagnostics.codeanalysis.dynamicallyaccessedmembersattribute?view=net-10.0 [3]: https://learn.microsoft.comdotnet/api/system.diagnostics.codeanalysis.dynamicdependencyattribute?view=net-10.0
Context: unoplatform/uno.chefs#1712 Context: #21920 Context: unoplatform/uno.extensions#2966 Context: unoplatform/uno.extensions#2969 Context: https://discord.com/channels/732297728826277939/732297837953679412/1444909702512119899 While running the uno.chefs app on macOS under NativeAOT: dotnet publish -c Release -r osx-x64 -f net10.0-desktop -p:TargetFrameworkOverride=net10.0-desktop -bl \ Chefs/Chefs.csproj \ -p:SelfContained=true -p:PublishAot=true -p:IsAotCompatible=true -p:UseSkiaRendering=true \ -p:IlcGenerateMapFile=true -p:IlcGenerateMstatFile=true -p:IlcGenerateDgmlFile=true \ -p:EmitCompilerGeneratedFiles=true -p:CompilerGeneratedFilesOutputPath=`pwd`/_gen Chefs/bin/Release/net10.0-desktop/osx-x64/publish/Chefs Console output would contain the following errors: fail: Uno.UI.Dispatching.NativeDispatcher[0] The [TabNavigation] property getter does not exist on type [Microsoft.UI.Xaml.Controls.FlipView] fail: Uno.UI.Dispatching.NativeDispatcher[0] The [Y] property getter does not exist on type [Windows.Foundation.Point] fail: Uno.UI.Dispatching.NativeDispatcher[0] The [X] property getter does not exist on type [Windows.Foundation.Point] At this point, a description of the relation between NativeAOT and Reflection would be useful. *Parts* of System.Reflection can be used. Which parts *can* be used, and how to use them, is oddly *not* well specified in the [Native AOT deployment][0] documentation. What doesn't work is outlined in the [limitations][1] section: > * No dynamic loading, for example, `Assembly.LoadFile`. > * No run-time code generation, for example, `System.Reflection.Emit`. What *always* appears to work is: * `Type.GetType()` returns a `Type` instance. What ***can*** work is: * `Type.GetType(string)`, *so long as* an assembly-qualified name is used as a *string constant* // bad string name = GetSomeTypeName(); Type type = Type.GetType(name); // good Type type = Type.GetType("My.Example.TypeName, ExampleAssembly"); * `Type.GetMethod(string)`, but see fine print below. * `Type.GetProperty(string)`, but see fine print below. * `MethodInfo.Invoke()` * … **The Fine Print**: in order for many Reflection-based APIs such as `Type.GetMethod(string)` to work, the program must contain "reflection metadata." We can now describe, briefly, what NativeAOT does: 1. Accept IL/assemblies as input 2. Internally trim the IL, optimizes them, etc. 3. Generates a native binary containing two separate bits of data: * Native machine code for execution * "reflection metadata" You can get an inkling of what methods survived trimming and become native code by using `dotnet publish -p:IlcGenerateMapFile=true …`, which will produce a `.mstat` file, a PE file which references every type and member which is in the native binary, ***after*** inlining. (Meaning if a member is inlined, it *won't* be present!) Use e.g. `monodis --memberref App.mstat` to list post-trimmed members. You can get an inkling of the "reflection metadata" that is present by using `dotnet publish -p:IlcGenerateMetadataLog=true …`, which produces a `.metadata.csv` file. It's not particularly scrutible, but can be used to verify observed behavior. Consider one of the above failure messages: The [TabNavigation] property getter does not exist on type [Microsoft.UI.Xaml.Controls.FlipView] An obvious question to ask: is the `TabNavigation` property in the reflection metadata? Search for `"TabNavigation"` (with quotes!), and it's *not* in there. Compare to when this commit is in use, and: 66087019, Property, "TabNavigation", "3410be9a 6810bea8 5410beae 5410beb2" 3410be9a, ConstantStringValue, "TabNavigation", "" The 3rd column is the property name `TabNavigation`, while the 2nd column is the type; for properties, we want `Property`. The 4th column is "Children", one of which is: 5410beae, MethodSemantics, "Getter : [HasThis] Microsoft.UI.Xaml.Input.KeyboardNavigationMode get_TabNavigation()", "50086c65" which at least verifies that `TabNavigation` is in play. Programs can control what is contained within reflection metadata by using string constants with Reflection APIs, for example: typeof(SomeKnownType).GetMethod("ConstantName") will ensure that reflection metadata contains `SomeKnownType.ConstantName()`, and `ConstantName()` will be returned from `typeof(SomeKnownType).GetMethods()` and can be invoked with `MethodInfo.Invoke()`. If you *don't* use `typeof(SomeKnownType)` or string constants with `Type.GetType(string)` or `type.GetMethod("constant")`, then you need to "suggest" that some pieces of information be added to reflection metadata. The two predominant ways to do this are via the custom attributes: * [`DynamicallyAccessedMembersAttribute`][2] * [`DynamicDependencyAttribute`][3] From the Uno perspective, enter `BindingPropertyHelper`: *everything* is based on Reflection! Fortunately NativeAOT "supports" Reflection! (See above.) *Unfortunately*, we now need to convince NativeAOT to store the reflection metadata Uno requires in order to work! Various other PRs have dealt with some of this, such as #21920. Returning to the original failure messages, e.g. fail: Uno.UI.Dispatching.NativeDispatcher[0] The [TabNavigation] property getter does not exist on type [Microsoft.UI.Xaml.Controls.FlipView] During the app build, `src/SourceGenerators/Uno.UI.SourceGenerators` generates a `BindableMetadata.g.cs` file which will mention `[Bindable]` types, e.g. for `Microsoft.UI.Xaml.Controls.FlipView`: /// <summary> /// Builder for Microsoft.UI.Xaml.Controls.FlipView /// </summary> [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute] static class MetadataBuilder_239 { internal static global::Uno.UI.DataBinding.IBindableType Build(global::Uno.UI.DataBinding.BindableType parent) { var bindableType = parent ?? new global::Uno.UI.DataBinding.BindableType(151, typeof(global::Microsoft.UI.Xaml.Controls.FlipView)); // … } private static object CreateInstance() => new global::Microsoft.UI.Xaml.Controls.FlipView(); } `BindableMetadata.g.cs` was needed in the ye olde Xamarin world order to tell *its* linker to e.g. "preserve the `FlipView` default ctor!" *Now*, we can build upon this infrastructure and update `BindableType` to request that Reflection information regarding properties, fields, and constructors be retained. We can do this by updating the `BindableType` constructor: partial class BindableType { internal const DynamicallyAccessedMemberTypes TypeRequirements = DynamicallyAccessedMemberTypes.PublicProperties | …; public BindableType(int estimatedPropertySize, [DynamicallyAccessedMembers(TypeRequirements)] Type sourceType) => … } *Because* we have `BindableMetadata.g.cs` *and* because it provides `typeof(FlipView)`, this directs NativeAOT to make property and other reflection metadata available to the app, *fixing* the error messages. Expand this addition of `[DynamicallyAccessedMembers]` to also include `BindableProperty` and related types. Note: the IL2111 warning "Method 'System.Type.TypeInitializer.get' …" is *caused by* `typeof(Type)` (?!), which is particularly odd given that we don't use `Type.TypeInitializer`! Thanks to Alexander Köplinger for [looking into this][4]. [0]: https://learn.microsoft.comdotnet/core/deploying/native-aot [1]: https://learn.microsoft.comdotnet/core/deploying/native-aot/#limitations-of-native-aot-deployment [2]: https://learn.microsoft.comdotnet/api/system.diagnostics.codeanalysis.dynamicallyaccessedmembersattribute?view=net-10.0 [3]: https://learn.microsoft.comdotnet/api/system.diagnostics.codeanalysis.dynamicdependencyattribute?view=net-10.0 [4]: https://discord.com/channels/732297728826277939/732297837953679412/1445773677152174150
Context: unoplatform/uno.chefs#1712 Context: #21920 Context: unoplatform/uno.extensions#2966 Context: unoplatform/uno.extensions#2969 Context: https://discord.com/channels/732297728826277939/732297837953679412/1444909702512119899 While running the uno.chefs app on macOS under NativeAOT: dotnet publish -c Release -r osx-x64 -f net10.0-desktop -p:TargetFrameworkOverride=net10.0-desktop -bl \ Chefs/Chefs.csproj \ -p:SelfContained=true -p:PublishAot=true -p:IsAotCompatible=true -p:UseSkiaRendering=true \ -p:IlcGenerateMapFile=true -p:IlcGenerateMstatFile=true -p:IlcGenerateDgmlFile=true \ -p:EmitCompilerGeneratedFiles=true -p:CompilerGeneratedFilesOutputPath=`pwd`/_gen Chefs/bin/Release/net10.0-desktop/osx-x64/publish/Chefs Console output would contain the following errors: fail: Uno.UI.Dispatching.NativeDispatcher[0] The [TabNavigation] property getter does not exist on type [Microsoft.UI.Xaml.Controls.FlipView] fail: Uno.UI.Dispatching.NativeDispatcher[0] The [Y] property getter does not exist on type [Windows.Foundation.Point] fail: Uno.UI.Dispatching.NativeDispatcher[0] The [X] property getter does not exist on type [Windows.Foundation.Point] At this point, a description of the relation between NativeAOT and Reflection would be useful. *Parts* of System.Reflection can be used. Which parts *can* be used, and how to use them, is oddly *not* well specified in the [Native AOT deployment][0] documentation. What doesn't work is outlined in the [limitations][1] section: > * No dynamic loading, for example, `Assembly.LoadFile`. > * No run-time code generation, for example, `System.Reflection.Emit`. What *always* appears to work is: * `Type.GetType()` returns a `Type` instance. What ***can*** work is: * `Type.GetType(string)`, *so long as* an assembly-qualified name is used as a *string constant* // bad string name = GetSomeTypeName(); Type type = Type.GetType(name); // good Type type = Type.GetType("My.Example.TypeName, ExampleAssembly"); * `Type.GetMethod(string)`, but see fine print below. * `Type.GetProperty(string)`, but see fine print below. * `MethodInfo.Invoke()` * … **The Fine Print**: in order for many Reflection-based APIs such as `Type.GetMethod(string)` to work, the program must contain "reflection metadata." We can now describe, briefly, what NativeAOT does: 1. Accept IL/assemblies as input 2. Internally trim the IL, optimizes them, etc. 3. Generates a native binary containing two separate bits of data: * Native machine code for execution * "reflection metadata" You can get an inkling of what methods survived trimming and become native code by using `dotnet publish -p:IlcGenerateMapFile=true …`, which will produce a `.mstat` file, a PE file which references every type and member which is in the native binary, ***after*** inlining. (Meaning if a member is inlined, it *won't* be present!) Use e.g. `monodis --memberref App.mstat` to list post-trimmed members. You can get an inkling of the "reflection metadata" that is present by using `dotnet publish -p:IlcGenerateMetadataLog=true …`, which produces a `.metadata.csv` file. It's not particularly scrutible, but can be used to verify observed behavior. Consider one of the above failure messages: The [TabNavigation] property getter does not exist on type [Microsoft.UI.Xaml.Controls.FlipView] An obvious question to ask: is the `TabNavigation` property in the reflection metadata? Search for `"TabNavigation"` (with quotes!), and it's *not* in there. Compare to when this commit is in use, and: 66087019, Property, "TabNavigation", "3410be9a 6810bea8 5410beae 5410beb2" 3410be9a, ConstantStringValue, "TabNavigation", "" The 3rd column is the property name `TabNavigation`, while the 2nd column is the type; for properties, we want `Property`. The 4th column is "Children", one of which is: 5410beae, MethodSemantics, "Getter : [HasThis] Microsoft.UI.Xaml.Input.KeyboardNavigationMode get_TabNavigation()", "50086c65" which at least verifies that `TabNavigation` is in play. Programs can control what is contained within reflection metadata by using string constants with Reflection APIs, for example: typeof(SomeKnownType).GetMethod("ConstantName") will ensure that reflection metadata contains `SomeKnownType.ConstantName()`, and `ConstantName()` will be returned from `typeof(SomeKnownType).GetMethods()` and can be invoked with `MethodInfo.Invoke()`. If you *don't* use `typeof(SomeKnownType)` or string constants with `Type.GetType(string)` or `type.GetMethod("constant")`, then you need to "suggest" that some pieces of information be added to reflection metadata. The two predominant ways to do this are via the custom attributes: * [`DynamicallyAccessedMembersAttribute`][2] * [`DynamicDependencyAttribute`][3] From the Uno perspective, enter `BindingPropertyHelper`: *everything* is based on Reflection! Fortunately NativeAOT "supports" Reflection! (See above.) *Unfortunately*, we now need to convince NativeAOT to store the reflection metadata Uno requires in order to work! Various other PRs have dealt with some of this, such as #21920. Returning to the original failure messages, e.g. fail: Uno.UI.Dispatching.NativeDispatcher[0] The [TabNavigation] property getter does not exist on type [Microsoft.UI.Xaml.Controls.FlipView] During the app build, `src/SourceGenerators/Uno.UI.SourceGenerators` generates a `BindableMetadata.g.cs` file which will mention `[Bindable]` types, e.g. for `Microsoft.UI.Xaml.Controls.FlipView`: /// <summary> /// Builder for Microsoft.UI.Xaml.Controls.FlipView /// </summary> [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute] static class MetadataBuilder_239 { internal static global::Uno.UI.DataBinding.IBindableType Build(global::Uno.UI.DataBinding.BindableType parent) { var bindableType = parent ?? new global::Uno.UI.DataBinding.BindableType(151, typeof(global::Microsoft.UI.Xaml.Controls.FlipView)); // … } private static object CreateInstance() => new global::Microsoft.UI.Xaml.Controls.FlipView(); } `BindableMetadata.g.cs` was needed in the ye olde Xamarin world order to tell *its* linker to e.g. "preserve the `FlipView` default ctor!" *Now*, we can build upon this infrastructure and update `BindableType` to request that Reflection information regarding properties, fields, and constructors be retained. We can do this by updating the `BindableType` constructor: partial class BindableType { internal const DynamicallyAccessedMemberTypes TypeRequirements = DynamicallyAccessedMemberTypes.PublicProperties | …; public BindableType(int estimatedPropertySize, [DynamicallyAccessedMembers(TypeRequirements)] Type sourceType) => … } *Because* we have `BindableMetadata.g.cs` *and* because it provides `typeof(FlipView)`, this directs NativeAOT to make property and other reflection metadata available to the app, *fixing* the error messages. Expand this addition of `[DynamicallyAccessedMembers]` to also include `BindableProperty` and related types. Note: the IL2111 warning "Method 'System.Type.TypeInitializer.get' …" is *caused by* `typeof(Type)` (?!), which is particularly odd given that we don't use `Type.TypeInitializer`! Thanks to Alexander Köplinger for [looking into this][4]. [0]: https://learn.microsoft.comdotnet/core/deploying/native-aot [1]: https://learn.microsoft.comdotnet/core/deploying/native-aot/#limitations-of-native-aot-deployment [2]: https://learn.microsoft.comdotnet/api/system.diagnostics.codeanalysis.dynamicallyaccessedmembersattribute?view=net-10.0 [3]: https://learn.microsoft.comdotnet/api/system.diagnostics.codeanalysis.dynamicdependencyattribute?view=net-10.0 [4]: https://discord.com/channels/732297728826277939/732297837953679412/1445773677152174150
Context: unoplatform/uno.chefs#1712 Context: #21920 Context: unoplatform/uno.extensions#2966 Context: unoplatform/uno.extensions#2969 Context: https://discord.com/channels/732297728826277939/732297837953679412/1444909702512119899 While running the uno.chefs app on macOS under NativeAOT: dotnet publish -c Release -r osx-x64 -f net10.0-desktop -p:TargetFrameworkOverride=net10.0-desktop -bl \ Chefs/Chefs.csproj \ -p:SelfContained=true -p:PublishAot=true -p:IsAotCompatible=true -p:UseSkiaRendering=true \ -p:IlcGenerateMapFile=true -p:IlcGenerateMstatFile=true -p:IlcGenerateDgmlFile=true \ -p:EmitCompilerGeneratedFiles=true -p:CompilerGeneratedFilesOutputPath=`pwd`/_gen Chefs/bin/Release/net10.0-desktop/osx-x64/publish/Chefs Console output would contain the following errors: fail: Uno.UI.Dispatching.NativeDispatcher[0] The [TabNavigation] property getter does not exist on type [Microsoft.UI.Xaml.Controls.FlipView] fail: Uno.UI.Dispatching.NativeDispatcher[0] The [Y] property getter does not exist on type [Windows.Foundation.Point] fail: Uno.UI.Dispatching.NativeDispatcher[0] The [X] property getter does not exist on type [Windows.Foundation.Point] At this point, a description of the relation between NativeAOT and Reflection would be useful. *Parts* of System.Reflection can be used. Which parts *can* be used, and how to use them, is oddly *not* well specified in the [Native AOT deployment][0] documentation. What doesn't work is outlined in the [limitations][1] section: > * No dynamic loading, for example, `Assembly.LoadFile`. > * No run-time code generation, for example, `System.Reflection.Emit`. What *always* appears to work is: * `Type.GetType()` returns a `Type` instance. What ***can*** work is: * `Type.GetType(string)`, *so long as* an assembly-qualified name is used as a *string constant* // bad string name = GetSomeTypeName(); Type type = Type.GetType(name); // good Type type = Type.GetType("My.Example.TypeName, ExampleAssembly"); * `Type.GetMethod(string)`, but see fine print below. * `Type.GetProperty(string)`, but see fine print below. * `MethodInfo.Invoke()` * … **The Fine Print**: in order for many Reflection-based APIs such as `Type.GetMethod(string)` to work, the program must contain "reflection metadata." We can now describe, briefly, what NativeAOT does: 1. Accept IL/assemblies as input 2. Internally trim the IL, optimizes them, etc. 3. Generates a native binary containing two separate bits of data: * Native machine code for execution * "reflection metadata" You can get an inkling of what methods survived trimming and become native code by using `dotnet publish -p:IlcGenerateMstatFile=true …`, which will produce a `.mstat` file, a PE file which references every type and member which is in the native binary, ***after*** inlining. (Meaning if a member is inlined, it *won't* be present!) Use e.g. `monodis --memberref App.mstat` to list post-trimmed members. You can get an inkling of the "reflection metadata" that is present by using `dotnet publish -p:IlcGenerateMetadataLog=true …`, which produces a `.metadata.csv` file. It's not particularly scrutible, but can be used to verify observed behavior. Consider one of the above failure messages: The [TabNavigation] property getter does not exist on type [Microsoft.UI.Xaml.Controls.FlipView] An obvious question to ask: is the `TabNavigation` property in the reflection metadata? Search for `"TabNavigation"` (with quotes!), and it's *not* in there. Compare to when this commit is in use, and: 66087019, Property, "TabNavigation", "3410be9a 6810bea8 5410beae 5410beb2" 3410be9a, ConstantStringValue, "TabNavigation", "" The 3rd column is the property name `TabNavigation`, while the 2nd column is the type; for properties, we want `Property`. The 4th column is "Children", one of which is: 5410beae, MethodSemantics, "Getter : [HasThis] Microsoft.UI.Xaml.Input.KeyboardNavigationMode get_TabNavigation()", "50086c65" which at least verifies that `TabNavigation` is in play. Programs can control what is contained within reflection metadata by using string constants with Reflection APIs, for example: typeof(SomeKnownType).GetMethod("ConstantName") will ensure that reflection metadata contains `SomeKnownType.ConstantName()`, and `ConstantName()` will be returned from `typeof(SomeKnownType).GetMethods()` and can be invoked with `MethodInfo.Invoke()`. If you *don't* use `typeof(SomeKnownType)` or string constants with `Type.GetType(string)` or `type.GetMethod("constant")`, then you need to "suggest" that some pieces of information be added to reflection metadata. The two predominant ways to do this are via the custom attributes: * [`DynamicallyAccessedMembersAttribute`][2] * [`DynamicDependencyAttribute`][3] From the Uno perspective, enter `BindingPropertyHelper`: *everything* is based on Reflection! Fortunately NativeAOT "supports" Reflection! (See above.) *Unfortunately*, we now need to convince NativeAOT to store the reflection metadata Uno requires in order to work! Various other PRs have dealt with some of this, such as #21920. Returning to the original failure messages, e.g. fail: Uno.UI.Dispatching.NativeDispatcher[0] The [TabNavigation] property getter does not exist on type [Microsoft.UI.Xaml.Controls.FlipView] During the app build, `src/SourceGenerators/Uno.UI.SourceGenerators` generates a `BindableMetadata.g.cs` file which will mention `[Bindable]` types, e.g. for `Microsoft.UI.Xaml.Controls.FlipView`: /// <summary> /// Builder for Microsoft.UI.Xaml.Controls.FlipView /// </summary> [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute] static class MetadataBuilder_239 { internal static global::Uno.UI.DataBinding.IBindableType Build(global::Uno.UI.DataBinding.BindableType parent) { var bindableType = parent ?? new global::Uno.UI.DataBinding.BindableType(151, typeof(global::Microsoft.UI.Xaml.Controls.FlipView)); // … } private static object CreateInstance() => new global::Microsoft.UI.Xaml.Controls.FlipView(); } `BindableMetadata.g.cs` was needed in the ye olde Xamarin world order to tell *its* linker to e.g. "preserve the `FlipView` default ctor!" *Now*, we can build upon this infrastructure and update `BindableType` to request that Reflection information regarding properties, fields, and constructors be retained. We can do this by updating the `BindableType` constructor: partial class BindableType { internal const DynamicallyAccessedMemberTypes TypeRequirements = DynamicallyAccessedMemberTypes.PublicProperties | …; public BindableType(int estimatedPropertySize, [DynamicallyAccessedMembers(TypeRequirements)] Type sourceType) => … } *Because* we have `BindableMetadata.g.cs` *and* because it provides `typeof(FlipView)`, this directs NativeAOT to make property and other reflection metadata available to the app, *fixing* the error messages. Expand this addition of `[DynamicallyAccessedMembers]` to also include `BindableProperty` and related types. Note: the IL2111 warning "Method 'System.Type.TypeInitializer.get' …" is *caused by* `typeof(Type)` (?!), which is particularly odd given that we don't use `Type.TypeInitializer`! Thanks to Alexander Köplinger for [looking into this][4]. [0]: https://learn.microsoft.comdotnet/core/deploying/native-aot [1]: https://learn.microsoft.comdotnet/core/deploying/native-aot/#limitations-of-native-aot-deployment [2]: https://learn.microsoft.comdotnet/api/system.diagnostics.codeanalysis.dynamicallyaccessedmembersattribute?view=net-10.0 [3]: https://learn.microsoft.comdotnet/api/system.diagnostics.codeanalysis.dynamicdependencyattribute?view=net-10.0 [4]: https://discord.com/channels/732297728826277939/732297837953679412/1445773677152174150
|
Given the investigation, what are possible fixes? A fix is the original approach in this PR: update An alternate approach may be to update the pattern used "downstream" in uno.chefs, so that instead of a foo.AddViewModelMapping(typeof(ShellModel), typeof(ShellViewModel));
// …
Unanswered question: what is the real API for this, how are the apps and generators updated to use it, etc. Possible answer: we could retain the existing "API" of |
|
Aside: this is dead code: It's dead code because This is in fact the unhanded exception I'm seeing, because |
|
Building upon alternate approaches, another alternate approach -- which maintains API and ABI -- is to update diff --git a/src/Uno.Extensions.Reactive.Generator/Bindables/BindableViewModelMappingGenerator.cs b/src/Uno.Extensions.Reactive.Generator/Bindables/BindableViewModelMappingGenerator.cs
index c71c55905..da99c8bf1 100644
--- a/src/Uno.Extensions.Reactive.Generator/Bindables/BindableViewModelMappingGenerator.cs
+++ b/src/Uno.Extensions.Reactive.Generator/Bindables/BindableViewModelMappingGenerator.cs
@@ -33,16 +33,30 @@ internal class BindableViewModelMappingGenerator : ICodeGenTool
{this.GetCodeGenAttribute()}
public static partial class ReactiveViewModelMappings
{{
+ private const global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes Types =
+ global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors
+ | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties
+ ;
+ private static global::System.Collections.Generic.KeyValuePair<global::System.Type, global::System.Type> E(
+ [global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(Types)] global::System.Type key,
+ [global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(Types)] global::System.Type value)
+ => new global::System.Collections.Generic.KeyValuePair<global::System.Type, global::System.Type>(key, value);
/// <summary>
/// Gets a mapping from a declared view model type (key) to its bindable counterpart type (value) generated by the feeds platform.
/// </summary>
/// <remarks>
/// This can be used in navigation engine to work only with the declared type while the navigation takes care to use the bindable friendly version.
/// </remarks>
- public static readonly global::System.Collections.Generic.IDictionary<global::System.Type, global::System.Type> ViewModelMappings = new global::System.Collections.Generic.Dictionary<global::System.Type, global::System.Type>
+ public static readonly global::System.Collections.Generic.IDictionary<global::System.Type, global::System.Type> ViewModelMappings;
+
+ static ReactiveViewModelMappings()
{{
- {_bindableVMs.Select(kvp => $"{{ typeof({kvp.Key}), typeof({kvp.Value}) }},").Align(6)}
- }};
+ var mappings = new global::System.Collections.Generic.Dictionary<global::System.Type, global::System.Type>();
+ var collection = (global::System.Collections.Generic.ICollection<
+ global::System.Collections.Generic.KeyValuePair<global::System.Type, global::System.Type>>) mappings;
+ {_bindableVMs.Select(kvp => $"collection.Add(E(typeof({kvp.Key}), typeof({kvp.Value})));").Align(6)}
+ ViewModelMappings = mappings;
+ }}
}}
}}".Align(0);
Resulting C# code is: namespace Chefs
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("BindableViewModelMappingGenerator", "1")]
public static partial class ReactiveViewModelMappings
{
private const global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes Types =
global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors
| global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties
;
private static global::System.Collections.Generic.KeyValuePair<global::System.Type, global::System.Type> E(
[global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(Types)] global::System.Type key,
[global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(Types)] global::System.Type value)
=> new global::System.Collections.Generic.KeyValuePair<global::System.Type, global::System.Type>(key, value);
/// <summary>
/// Gets a mapping from a declared view model type (key) to its bindable counterpart type (value) generated by the feeds platform.
/// </summary>
/// <remarks>
/// This can be used in navigation engine to work only with the declared type while the navigation takes care to use the bindable friendly version.
/// </remarks>
public static readonly global::System.Collections.Generic.IDictionary<global::System.Type, global::System.Type> ViewModelMappings;
static ReactiveViewModelMappings()
{
var mappings = new global::System.Collections.Generic.Dictionary<global::System.Type, global::System.Type>();
var collection = (global::System.Collections.Generic.ICollection<
global::System.Collections.Generic.KeyValuePair<global::System.Type, global::System.Type>>) mappings;
collection.Add(E(typeof(Chefs.Presentation.CookbookDetailModel), typeof(global::Chefs.Presentation.CookbookDetailViewModel)));
collection.Add(E(typeof(Chefs.Presentation.CreateUpdateCookbookModel), typeof(global::Chefs.Presentation.CreateUpdateCookbookViewModel)));
collection.Add(E(typeof(Chefs.Presentation.FavoriteRecipesModel), typeof(global::Chefs.Presentation.FavoriteRecipesViewModel)));
collection.Add(E(typeof(Chefs.Presentation.FilterModel), typeof(global::Chefs.Presentation.FilterViewModel)));
collection.Add(E(typeof(Chefs.Presentation.GenericDialogModel), typeof(global::Chefs.Presentation.GenericDialogViewModel)));
collection.Add(E(typeof(Chefs.Presentation.HomeModel), typeof(global::Chefs.Presentation.HomeViewModel)));
collection.Add(E(typeof(Chefs.Presentation.LiveCookingModel), typeof(global::Chefs.Presentation.LiveCookingViewModel)));
collection.Add(E(typeof(Chefs.Presentation.LoginModel), typeof(global::Chefs.Presentation.LoginViewModel)));
collection.Add(E(typeof(Chefs.Presentation.MainModel), typeof(global::Chefs.Presentation.MainViewModel)));
collection.Add(E(typeof(Chefs.Presentation.MapModel), typeof(global::Chefs.Presentation.MapViewModel)));
collection.Add(E(typeof(Chefs.Presentation.NotificationsModel), typeof(global::Chefs.Presentation.NotificationsViewModel)));
collection.Add(E(typeof(Chefs.Presentation.ProfileModel), typeof(global::Chefs.Presentation.ProfileViewModel)));
collection.Add(E(typeof(Chefs.Presentation.RecipeDetailsModel), typeof(global::Chefs.Presentation.RecipeDetailsViewModel)));
collection.Add(E(typeof(Chefs.Presentation.RegistrationModel), typeof(global::Chefs.Presentation.RegistrationViewModel)));
collection.Add(E(typeof(Chefs.Presentation.SearchModel), typeof(global::Chefs.Presentation.SearchViewModel)));
collection.Add(E(typeof(Chefs.Presentation.SettingsModel), typeof(global::Chefs.Presentation.SettingsViewModel)));
collection.Add(E(typeof(Chefs.Presentation.ShellModel), typeof(global::Chefs.Presentation.ShellViewModel)));
collection.Add(E(typeof(Chefs.Presentation.WelcomeModel), typeof(global::Chefs.Presentation.WelcomeViewModel)));
ViewModelMappings = mappings;
}
}
}The change is to use This allows things to work without the original What I'm still uncertain about is whether it's the correct fix. |
|
The benefit to using The downside is that it should (technically) be slower, as its going through an interface invocation instead of hitting We could change what the generator produces to still not add new API while avoiding interface invocations. Another approach would be to add new API: namespace Uno.Extensions.Reactive;
public class ModelToViewModelMapping : IDictionary<Type, Type> {
public void Add([DAM(…)] Type key, [DAM(…)] Type value) => …
}which would allow minimal changes to the generator -- it would emit We thus have 2-3 approaches to preserving constructors on
Given these three approaches, and given that it's |
Yes definitely the best approach IMO. That dictionary is generated for the navigation to be able to know which VM to create when the user attempts to navigate to a given model type. About the 100% retro compatible. |
Actually thinking again about it, I think the We are not supposed to create dynamically instances of the model itself (i.e. the |
51509ec to
71cd426
Compare
*ViewModel constructors, properties
Context: unoplatform/uno@559b1f7 While running the uno.chefs app on macOS under NativeAOT: dotnet publish -c Release -r osx-x64 -f net10.0-desktop -p:TargetFrameworkOverride=net10.0-desktop -bl \ Chefs/Chefs.csproj \ -p:SelfContained=true -p:PublishAot=true -p:IsAotCompatible=true -p:UseSkiaRendering=true \ -p:IlcGenerateMapFile=true -p:IlcGenerateMstatFile=true -p:IlcGenerateDgmlFile=true -p:IlcGenerateMetadataLog=true \ -p:EmitCompilerGeneratedFiles=true -p:CompilerGeneratedFilesOutputPath=`pwd`/_gen Chefs/bin/Release/net10.0-desktop/osx-x64/publish/Chefs Console output would contain the following warnings: fail: Uno.UI.Dispatching.NativeDispatcher[0] NativeDispatcher unhandled exception System.InvalidOperationException: A suitable constructor for type 'Chefs.Presentation.ShellViewModel' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor. With a local patch in `Uno.dll`, we see that the above `InvalidOperationException` is from Microsoft.Extensions.DependencyInjection: fail: Uno.UI.Dispatching.NativeDispatcher[0] NativeDispatcher unhandled exception System.InvalidOperationException: A suitable constructor for type 'Chefs.Presentation.ShellViewModel' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor. at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(ResultCache, ServiceIdentifier, Type, CallSiteChain) + 0x3e9 at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateExact(ServiceDescriptor, ServiceIdentifier, CallSiteChain, Int32) + 0x181 at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceIdentifier, CallSiteChain) + 0x14f at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(ServiceIdentifier serviceIdentifier, CallSiteChain callSiteChain) + 0xc9 at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.GetCallSite(ServiceIdentifier, CallSiteChain) + 0x92 at Microsoft.Extensions.DependencyInjection.ServiceProvider.CreateServiceAccessor(ServiceIdentifier serviceIdentifier) + 0x68 at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey, Func`2) + 0xd2 at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(ServiceIdentifier, ServiceProviderEngineScope) + 0x39 at Uno.Extensions.Navigation.Navigators.ControlNavigator.<>c__DisplayClass8_0.<<CreateViewModel>b__0>d.MoveNext() + 0x80 The problem is that trimming removed the `ShellViewModel` constructor, along with other members. Question: why were these members removed? Answer 1: because the members weren't *used*, via static analysis. This is the case here, as there is no `new ShellViewModel()` within `uno.chefs` or its generated code. Answer 2: Even when "Answer 1" isn't true, the linker *not* removing a member *doesn't* mean that the member can be accessed through Reflection! Reflection requires a different set of data. We thus need to convince the trimmer to not only preserve the members, but also to allow the members to be used through Reflection. From unoplatform/uno@559b1f78: > The two predominant ways to do this are via the custom attributes: > > * `DynamicallyAccessedMembersAttribute` > * `DynamicDependencyAttribute` At this point we know we have e.g. `ShellModel` (user-written code), and `ShellViewModel` (from `src/Uno.Extensions.Reactive.Generator`). How are these types associated with each other at runtime? They're associated with each other at runtime via a dictionary generated by `BindableViewModelMappingGenerator`: // generated code namespace Chefs; partial class ReactiveViewModelMappings { public static readonly IDictionary<Type, Type> ViewModelMappings = new Dictionary<Type, Type> { { typeof(ShellModel), typeof(ShellViewModel) }, // … }; } Use of `IViewRegistry.FindViewByModel<T>()` allows finding the latter from the former, e.g. `views.FindViewByModel<ShellModel>()` returns a `MappedViewMap` with `MappedViewMap.MappedViewModel` being `ShellViewModel`: Console.WriteLine(views.FindByViewModel<ShellModel>()); // MappedViewMap { View = , ViewSelector = , ViewModel = Chefs.Presentation.ShellModel, Data = , ResultData = , ViewAttributes = , MappedViewModel = Chefs.Presentation.ShellViewModel } To preserve the `ShellViewModel` members, where do we put `[DynamicallyAccessedMembers]` or `[DynamicDependency]`? There are two plausible places: 1. On `ModelAttribute` and `IModel<T>` 2. Within `BindableViewModelMappingGenerator`-generated code. (1) `ModelAttribute` and `IModel<T>` are viable locations because `ViewModelGenTool_*` emits partial types for e.g. `ShellModel`: // generated code [Model(typeof(ShellViewModel))] partial record ShellModel : IModel<ShellViewModel> { } While this works, it also means that using `[Model]`/`IModel<T>` without MVUX would result in app size increases. Which leaves (2): update `BindableViewModelMappingGenerator` so that a codepath uses `[DynamicallyAccessedMembers]`. Update `BindableViewModelMappingGenerator` to instead emit code similar to: namespace Chefs; static partial class ReactiveViewModelMappings { private const DynamicallyAccessedMemberTypes Types = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties ; private static void AddMapping( Dictionary<Type, Type> dict, Type key, [DynamicallyAccessedMembers(Types)] Type value) => dict.Add(key, value); public static readonly IDictionary<Type, Type> ViewModelMappings; static ReactiveViewModelMappings() { var mappings = new Dictionary<Type, Type>(); AddMapping(mappings, typeof(Chefs.Presentation.ShellModel), typeof(global::Chefs.Presentation.ShellViewModel)); // … ViewModelMappings = mappings; } } The introduction of `ReactiveViewModelMappings.AddMapping()` allows a place to provide `[DynamicallyAccessedMembers]`, which in turn allows the trimmer to preserve constructors and properties on `ShellViewModel`, which fixes the `InvalidOperationException`. Note that properties must also be preserved, as if only constructors are preserved we will see this failure message: fail: Uno.UI.DataBinding.BindingPropertyHelper[0] The [Pages] property getter does not exist on type [Chefs.Presentation.WelcomeViewModel]
71cd426 to
30d79ed
Compare
Context: unoplatform/uno@559b1f7
While running the uno.chefs app on macOS under NativeAOT:
Console output would contain the following warnings:
With a local patch in
Uno.dll, we see that the aboveInvalidOperationExceptionis fromMicrosoft.Extensions.DependencyInjection:
The problem is that trimming removed the
ShellViewModelconstructor,along with other members.
Question: why were these members removed?
Answer 1: because the members weren't used, via static analysis.
This is the case here, as there is no
new ShellViewModel()withinuno.chefsor its generated code.Answer 2: Even when "Answer 1" isn't true, the linker not removing
a member doesn't mean that the member can be accessed through
Reflection! Reflection requires a different set of data.
We thus need to convince the trimmer to not only preserve the members,
but also to allow the members to be used through Reflection.
From unoplatform/uno@559b1f78:
At this point we know we have e.g.
ShellModel(user-written code),and
ShellViewModel(fromsrc/Uno.Extensions.Reactive.Generator).How are these types associated with each other at runtime?
They're associated with each other at runtime via a dictionary
generated by
BindableViewModelMappingGenerator:Use of
IViewRegistry.FindViewByModel<T>()allows finding the latterfrom the former, e.g.
views.FindViewByModel<ShellModel>()returns aMappedViewMapwithMappedViewMap.MappedViewModelbeingShellViewModel:To preserve the
ShellViewModelmembers, where do we put[DynamicallyAccessedMembers]or[DynamicDependency]?There are two plausible places:
ModelAttributeandIModel<T>BindableViewModelMappingGenerator-generated code.(1)
ModelAttributeandIModel<T>are viable locations becauseViewModelGenTool_*emits partial types for e.g.ShellModel:While this works, it also means that using
[Model]/IModel<T>without MVUX would result in app size increases.
Which leaves (2): update
BindableViewModelMappingGeneratorso thata codepath uses
[DynamicallyAccessedMembers].Update
BindableViewModelMappingGeneratorto instead emit codesimilar to:
The introduction of
ReactiveViewModelMappings.AddMapping()allowsa place to provide
[DynamicallyAccessedMembers], which in turnallows the trimmer to preserve constructors and properties on
ShellViewModel, which fixes theInvalidOperationException.Note that properties must also be preserved, as if only constructors
are preserved we will see this failure message: