Skip to content

Migrate AvaloniaNameSourceGenerator to IIncrementalGenerator #19216

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: master
Choose a base branch
from

Conversation

maxkatz6
Copy link
Member

@maxkatz6 maxkatz6 commented Jul 8, 2025

What does the pull request do?

It was a bit long overdue.

Breaking changes

It raises minimal supported Visual Studio / .NET SDK version (only for source generators, not affecting anything else).

  • Visual Studio 2022+
  • .NET SDK 6.0+

@avaloniaui-bot
Copy link

You can test this PR using the following package version. 12.0.999-cibuild0057596-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@avaloniaui-bot
Copy link

You can test this PR using the following package version. 12.0.999-cibuild0057610-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

var optionsProvider = pair.Right.Right;
var filePath = text.Path;

if (!(filePath.EndsWith(".xaml", StringComparison.OrdinalIgnoreCase) ||
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably add some compiler-visible MSBuild metadata for XAML items that are going to be compiled (right now the actual check is the item being AvaloniaResource with the plan to have it restricted to AvaloniaXaml). It should be available before CoreCompile target, so we can pass that info into the analyzer.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is an extra metadata check already.
We read build_metadata.AdditionalFiles.SourceItemGroup metadata in the same file below, and for XAML files we set it as AvaloniaXaml.
See

<AdditionalFiles Include="@(AvaloniaXaml)" SourceItemGroup="AvaloniaXaml" />

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We had to add this metadata to avoid conflicts with MAUI XAML that also uses generators

})
.Where(tuple => tuple.textContent is not null);

var generatorInput = xamlFiles.Combine(context.CompilationProvider);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC passing CSharpCompilation to RegisterSourceOutput will trigger generator to run on any change in the entire project, which kinda defeats the purpose of generator being incremental.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly, but we need compilation provider, to have access to type system. Caching it might potentially lead to invalid state.
There can be a point in parsing XAML and reading x:Name metadata without type information, but it would be much more involving refactoring of the compiler.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK, the CompilationProvider updates on every keystroke in source, so a Combine will rerun the latter portion of the pipeline on every keystroke (the entire RegisterSourceOutput in this case). This can be somewhat mitigated with frequent checking of the CancellationToken to bail out.

Normally, I gather type information from symbols during the transform part of ForAttributeWithMetadataName into an equatable model, but this is an AdditionalTextsProvider-based generator.

You could create another intermediate stage where you parse the XAML and return an equatable model containing unresolved types (fully-qualified names and ones that need additional resolution, such as a using with a group of namespaces). Then the next stage of the pipeline with CompilationProvider only needs to resolve types. Though in a large project, especially with automation, there could be thousands of named controls to resolve on keystroke. Tough problem.

Copy link
Member Author

@maxkatz6 maxkatz6 Jul 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed changes to only update compilation instance when any dependency assembly was added or changed.

Copy link
Member Author

@maxkatz6 maxkatz6 Jul 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stevemonaco just noticed your comment.
Yes, I tried to make XamlX work with unresolved types. Where I created a read-only type system containing pre-computed referenced assemblies. And treat any type as fake type, resolved after parsing on separated step.
I eventually gave up, when realized issues with conflicting XmlnsDefinition. Single XAML namespace can be mapped on multiple CLR namespaces, and XamlX can't delay this resolution until later.
I wasn't planning to refactor XamlX while making this PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, tried a bit different approach with parsing and resolving XML types first, without compilation info.
And then another step with up-to-date compilation combined used to resolve actual types.
Need to understand performance difference though.

…on should be reused between dependency changes
@avaloniaui-bot
Copy link

You can test this PR using the following package version. 12.0.999-cibuild0057630-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@maxkatz6 maxkatz6 requested a review from kekekeks July 9, 2025 06:51
@avaloniaui-bot
Copy link

You can test this PR using the following package version. 12.0.999-cibuild0057638-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@avaloniaui-bot
Copy link

You can test this PR using the following package version. 12.0.999-cibuild0057646-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@avaloniaui-bot
Copy link

You can test this PR using the following package version. 12.0.999-cibuild0057910-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

var genericTypeName = typeAgs.Count == 0
? $"global::{typeName}"
: $"global::{typeName}<{string.Join(", ", typeAgs.Select(arg => $"global::{arg}"))}>";
return new ResolvedName(genericTypeName, name, fieldModifier);
}

IXamlAstNode IXamlAstVisitor.Visit(IXamlAstNode node)
Copy link
Member

@MrJul MrJul Aug 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we pass a cancellation token to this class and check it here in Visit to avoid continuing to parse files unnecessary?

}

IXamlAstNode IXamlAstVisitor.Visit(IXamlAstNode node)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we pass a cancellation token to this class and check it here in Visit to avoid continuing to parse files unnecessary?

{
var resolvedNames = new List<ResolvedName>();
foreach (var xmlName in xmlView.XmlNames)
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cancellation token should probably be checked in this loop. (If done the catch block below should ignore OperationCanceledException)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants