From ec47ea9e2afed1d6a3361ac4d5c25b5ae3734ef6 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sun, 28 May 2023 23:07:07 -0400 Subject: [PATCH] Implement support for x:ClassModifier="internal" and TryLoad generators --- .../XamlCompilerTaskExecutor.cs | 35 ++++++++++++++++++- .../AvaloniaXamlIlRuntimeCompiler.cs | 4 +-- .../XamlDocumentResource.cs | 3 ++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs index d71070e818..898c23973c 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs +++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs @@ -288,6 +288,24 @@ namespace Avalonia.Build.Tasks if (precompileText != "true") throw new XamlParseException("Invalid value for x:Precompile", precompileDirective); } + + var classModifierDirective = initialRoot.Children.OfType() + .FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "ClassModifier"); + bool? classModifierPublic = null; + if (classModifierDirective != null) + { + var classModifierText = (classModifierDirective.Values[0] as XamlAstTextNode)?.Text.Trim() + .ToLowerInvariant(); + if ("Public".Equals(classModifierText, StringComparison.OrdinalIgnoreCase)) + classModifierPublic = true; + // XAML spec uses "Public" and "NotPublic" values, + // When WPF documentation uses "public" and "internal". + else if ("NotPublic".Equals(classModifierText, StringComparison.OrdinalIgnoreCase) + || "Internal".Equals(classModifierText, StringComparison.OrdinalIgnoreCase)) + classModifierPublic = false; + else + throw new XamlParseException("Invalid value for x:ClassModifier. Expected value are: Public, NotPublic.", precompileDirective); + } var classDirective = initialRoot.Children.OfType() .FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "Class"); @@ -297,8 +315,21 @@ namespace Avalonia.Build.Tasks if (classDirective.Values.Count != 1 || !(classDirective.Values[0] is XamlAstTextNode tn)) throw new XamlParseException("x:Class should have a string value", classDirective); classType = typeSystem.TargetAssembly.FindType(tn.Text); + if (classType == null) throw new XamlParseException($"Unable to find type `{tn.Text}`", classDirective); + + var isClassPublic = typeSystem.GetTypeReference(classType).Resolve().IsPublic; + classModifierPublic ??= isClassPublic; + + // We do not really need x:ClassModifier support for x:Class, but we can at least use it for validation here. + if (classModifierPublic != isClassPublic) + { + throw new XamlParseException( + "XAML file x:ClassModifier doesn't match the x:Class type modifiers.", + precompileDirective); + } + compiler.OverrideRootType(parsed, new XamlAstClrTypeReference(classDirective, classType, false)); initialRoot.Children.Remove(classDirective); @@ -319,6 +350,7 @@ namespace Avalonia.Build.Tasks ((List)parsedXamlDocuments).Add(new XamlDocumentResource( parsed, res.Uri, res, classType, + classModifierPublic ?? true, populateBuilder, compiler.DefinePopulateMethod(populateBuilder, parsed, populateName, classTypeDefinition == null), @@ -502,7 +534,8 @@ namespace Avalonia.Build.Tasks } - if (document.BuildMethod != null || classTypeDefinition != null) + if (document.IsPublic + && (document.BuildMethod != null || classTypeDefinition != null)) { var compiledBuildMethod = document.BuildMethod == null ? null : diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs index e3d154ffdd..e2aab7d685 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs @@ -231,8 +231,8 @@ namespace Avalonia.Markup.Xaml.XamlIl ?? ((IXamlAstValueNode)parsed.Root).Type.GetClrType().Name; var tb = _sreBuilder.DefineType("Builder_" + Guid.NewGuid().ToString("N") + "_" + xamlName); var builder = _sreTypeSystem.CreateTypeBuilder(tb); - parsedDocuments.Add(new XamlDocumentResource(parsed, document.BaseUri?.ToString(), null, null, - builder, + parsedDocuments.Add(new XamlDocumentResource(parsed, document.BaseUri?.ToString(), + null, null, true, builder, compiler.DefinePopulateMethod(builder, parsed, AvaloniaXamlIlCompiler.PopulateName, true), document.RootInstance is null ? compiler.DefineBuildMethod(builder, parsed, AvaloniaXamlIlCompiler.BuildName, true) : null)); originalDocuments.Add(document); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentResource.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentResource.cs index d5d452a9f3..932fe067af 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentResource.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentResource.cs @@ -13,6 +13,7 @@ internal class XamlDocumentResource : IXamlDocumentResource string? uri, IFileSource? fileSource, IXamlType? classType, + bool isPublic, IXamlTypeBuilder typeBuilder, IXamlMethodBuilder populateMethod, IXamlMethodBuilder? buildMethod) @@ -21,6 +22,7 @@ internal class XamlDocumentResource : IXamlDocumentResource Uri = uri; FileSource = fileSource; ClassType = classType; + IsPublic = isPublic; TypeBuilder = typeBuilder; PopulateMethod = populateMethod; BuildMethod = buildMethod; @@ -31,6 +33,7 @@ internal class XamlDocumentResource : IXamlDocumentResource public IFileSource? FileSource { get; } public IXamlType? ClassType { get; } + public bool IsPublic { get; } public IXamlTypeBuilder TypeBuilder { get; } public IXamlMethodBuilder PopulateMethod { get; } public IXamlMethodBuilder? BuildMethod { get; }