unoplatform/uno

Incompatibility with CommunityToolkit.Mvvm SourceGeneration

Closed this issue ยท 5 comments

Current behavior

Auto-generate an observable property like this:

[ObservableProperty]
private DateTimeOffset? _minDate;

Do a two-way binding on this property:

<CalendarDatePicker Date="{x:Bind ViewModel.MinDate, Mode=TwoWay}" />

Compilation throws an error:

Rebuild started...
1>------ Rebuild All started: Project: repro.Mobile, Configuration: Debug Any CPU ------
Restored E:\repos\_local\repro\repro.Mobile\repro.Mobile.csproj (in 36 ms).
1>CSC : error UXAML0001: Unable to find member [MinDate] on type [repro.Mobile.RandomViewModel]
1>E:\repos\_local\repro\repro.Shared\MainPage.xaml(6,6,6,6): error UXAML0001: An error was found in Grid
1>E:\repos\_local\repro\repro.Shared\MainPage.xaml(1,2,1,2): error UXAML0001: An error was found in Page
1>CSC : error UXAML0001: Processing failed for file E:\repos\_local\repro\repro.Shared\MainPage.xaml (Uno.UI.SourceGenerators.XamlGenerator.XamlParsingException: An error was found in Page ---> Uno.UI.SourceGenerators.XamlGenerator.XamlParsingException: An error was found in Grid ---> System.InvalidOperationException: Unable to find member [MinDate] on type [repro.Mobile.RandomViewModel]
1>E:\repos\_local\repro\repro.Shared\MainPage.xaml.cs(14,18,14,37): error CS1061: 'MainPage' does not contain a definition for 'InitializeComponent' and no accessible extension method 'InitializeComponent' accepting a first argument of type 'MainPage' could be found (are you missing a using directive or an assembly reference?)
1>E:\repos\_local\repro\repro.Shared\App.xaml.cs(26,18,26,37): error CS1061: 'App' does not contain a definition for 'InitializeComponent' and no accessible extension method 'InitializeComponent' accepting a first argument of type 'App' could be found (are you missing a using directive or an assembly reference?)
1>Done building project "repro.Mobile.csproj" -- FAILED.
========== Rebuild All: 0 succeeded, 1 failed, 0 skipped ==========

Expected behavior

Compilation should succeed

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

I attached a minimal repro solution here
repro.zip

Workaround

This only happens for two-way bindings that have an auto-generated backing property that is defined like this:

[ObservableProperty]
private DateTimeOffset? _minDate;

If I define the property in my code like the follow, it works fine:

private DateTimeOffset? _minDate;

public DateTimeOffset? MinDate
{
    get => _minDate;
    set => SetProperty(ref _minDate, value);
}

Works on UWP/WinUI

Yes

Environment

Uno.WinUI / Uno.WinUI.WebAssembly / Uno.WinUI.Skia, Uno.SourceGenerationTasks

NuGet package version(s)

4.5.9

Affected platforms

Build tasks, Solution Templates

IDE

Visual Studio 2022, Visual Studio for Mac

IDE version

No response

Relevant plugins

No response

Anything else we need to know?

No response

Thanks for the report. This is a limitation of the way C# source generators work, where it's not possible for a generator to depend on another. This is not something the Uno "embedded" Generators can work around at this time.

You can fallback to a two-phase generation using Uno.SourceGenerationTasks, by setting <UnoUIUseRoslynSourceGenerators>false</UnoUIUseRoslynSourceGenerators>, but note that this has a build performance impact.

Hi, I've been searching for this for hours as related errors were hidden by another one (never use a namespace named Whatever.Windows, generated code doesn't like that and search for Windows.UI.* in here....).
Anyway, I'll give a try to this workaround, but this is really sad since build time is awesome without it ๐Ÿ˜…
What about a "meta generator" that would force order?

A "meta generator" really is what Uno.SourceGenerationTasks is about, it's "roslyn before roslyn" in a sense. Let's hope the roslyn team will consider this in the future.

I forgot about that ๐Ÿ˜ Thanks!

For those interested, I'm currently using this workaround in my shared project (projitems).
Note that you'll probably have to tweak it (at least regarding the target framework... did that quickly ;) ).

The goal is to have a first pass generating Community Toolkit's MVVM files, and then rebuild for Uno to pick them by including them in the project as if they were "standard" files (and they will not then be regenerated as it would fail - since they exist already).

Drawback: I didn't add a cleanup task so you'll have to delete the "generated" folder everytime you want MVVM Analyzers to regenerate those.
It's a first step and clearly a workaround. It has probably some side effects (like sometimes having to restart VS for the files to be loaded properly - happens at least once on my side), but you can then use Roslyn generators as usual, which are way faster than Uno's.

  <Target Name="MVVMUnoWorkaround1" AfterTargets="MVVMToolkitGatherAnalyzers" Condition="'$(TargetFramework)' != 'net6.0-windows10.0.19041.0' And !Exists('obj\generated\CommunityToolkit.Mvvm.SourceGenerators')">
    <Message Importance="high" Text="Workaround for MVVM SourceGenerators with Uno (phase 1/2): keeping MVVM generated files. Another build will be required once done!!!" />
    <PropertyGroup>
      <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
      <CompilerGeneratedFilesOutputPath>obj\generated</CompilerGeneratedFilesOutputPath>
    </PropertyGroup>
  </Target>
  <Target Name="MVVMUnoWorkaround2" AfterTargets="MVVMToolkitGatherAnalyzers" Condition="'$(TargetFramework)' != 'net6.0-windows10.0.19041.0' And Exists('obj\generated\CommunityToolkit.Mvvm.SourceGenerators')">
    <Message Importance="high" Text="Workaround for MVVM SourceGenerators with Uno (phase 2/2): removing the MVVM Analyzer since code has been generated already." />
    <ItemGroup>
      <Analyzer Remove="@(MVVMToolkitAnalyzer)" />
    </ItemGroup>
  </Target>
  <ItemGroup>
    <Compile Include="obj\generated\CommunityToolkit.Mvvm.SourceGenerators\**\*.cs" Condition="'$(TargetFramework)' != 'net6.0-windows10.0.19041.0' And Exists('obj\generated\CommunityToolkit.Mvvm.SourceGenerators')" />
  </ItemGroup>