diff --git a/src/Controls/samples/Controls.Sample/Maui.Controls.Sample.csproj b/src/Controls/samples/Controls.Sample/Maui.Controls.Sample.csproj index ebf4fca2e869..80a8fdcad902 100644 --- a/src/Controls/samples/Controls.Sample/Maui.Controls.Sample.csproj +++ b/src/Controls/samples/Controls.Sample/Maui.Controls.Sample.csproj @@ -51,6 +51,13 @@ + + + + diff --git a/src/Controls/src/SourceGen/CodeBehindGenerator.cs b/src/Controls/src/SourceGen/CodeBehindGenerator.cs index 64b1d459489c..556ebc499b4e 100644 --- a/src/Controls/src/SourceGen/CodeBehindGenerator.cs +++ b/src/Controls/src/SourceGen/CodeBehindGenerator.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -44,20 +45,25 @@ public void Initialize(IncrementalGeneratorInitializationContext initContext) var xmlnsDefinitionsProvider = initContext.CompilationProvider .Select(GetAssemblyAttributes); + var typeCacheProvider = initContext.CompilationProvider + .Select(GetTypeCache); + var sourceProvider = projectItemProvider .Combine(xmlnsDefinitionsProvider) + .Combine(typeCacheProvider) .Combine(initContext.CompilationProvider) .Select(static (t, _) => (t.Left.Left, t.Left.Right, t.Right)); initContext.RegisterSourceOutput(sourceProvider, static (sourceProductionContext, provider) => { - var (projectItem, caches, compilation) = provider; + var ((projectItem, xmlnsCache), typeCache, compilation) = provider; if (projectItem == null) return; + switch (projectItem.Kind) { case "Xaml": - GenerateXamlCodeBehind(projectItem, compilation, sourceProductionContext, caches); + GenerateXamlCodeBehind(projectItem, compilation, sourceProductionContext, xmlnsCache, typeCache); break; case "Css": GenerateCssCodeBehind(projectItem, sourceProductionContext); @@ -137,7 +143,12 @@ static AssemblyCaches GetAssemblyAttributes(Compilation compilation, Cancellatio return new AssemblyCaches(xmlnsDefinitions, internalsVisible); } - static void GenerateXamlCodeBehind(ProjectItem projItem, Compilation compilation, SourceProductionContext context, AssemblyCaches caches) + static IDictionary GetTypeCache(Compilation compilation, CancellationToken cancellationToken) + { + return new Dictionary(); + } + + static void GenerateXamlCodeBehind(ProjectItem projItem, Compilation compilation, SourceProductionContext context, AssemblyCaches caches, IDictionary typeCache) { var text = projItem.AdditionalText.GetText(context.CancellationToken); if (text == null) @@ -149,7 +160,7 @@ static void GenerateXamlCodeBehind(ProjectItem projItem, Compilation compilation return; var uid = Crc64.ComputeHashString($"{compilation.AssemblyName}.{itemName}"); - if (!TryParseXaml(text, uid, compilation, caches, context.CancellationToken, projItem.TargetFramework, out var accessModifier, out var rootType, out var rootClrNamespace, out var generateDefaultCtor, out var addXamlCompilationAttribute, out var hideFromIntellisense, out var XamlResourceIdOnly, out var baseType, out var namedFields, out var parseException)) + if (!TryParseXaml(text, uid, compilation, caches, typeCache, context.CancellationToken, projItem.TargetFramework, out var accessModifier, out var rootType, out var rootClrNamespace, out var generateDefaultCtor, out var addXamlCompilationAttribute, out var hideFromIntellisense, out var XamlResourceIdOnly, out var baseType, out var namedFields, out var parseException)) { if (parseException != null) { @@ -237,7 +248,7 @@ static void GenerateXamlCodeBehind(ProjectItem projItem, Compilation compilation context.AddSource(hintName, SourceText.From(sb.ToString(), Encoding.UTF8)); } - static bool TryParseXaml(SourceText text, string uid, Compilation compilation, AssemblyCaches caches, CancellationToken cancellationToken, string? targetFramework, out string? accessModifier, out string? rootType, out string? rootClrNamespace, out bool generateDefaultCtor, out bool addXamlCompilationAttribute, out bool hideFromIntellisense, out bool xamlResourceIdOnly, out string? baseType, out IEnumerable<(string, string, string)>? namedFields, out Exception? exception) + static bool TryParseXaml(SourceText text, string uid, Compilation compilation, AssemblyCaches caches, IDictionary typeCache, CancellationToken cancellationToken, string? targetFramework, out string? accessModifier, out string? rootType, out string? rootClrNamespace, out bool generateDefaultCtor, out bool addXamlCompilationAttribute, out bool hideFromIntellisense, out bool xamlResourceIdOnly, out string? baseType, out IEnumerable<(string, string, string)>? namedFields, out Exception? exception) { cancellationToken.ThrowIfCancellationRequested(); @@ -320,9 +331,9 @@ static bool TryParseXaml(SourceText text, string uid, Compilation compilation, A return true; } - namedFields = GetNamedFields(root, nsmgr, compilation, caches, cancellationToken); + namedFields = GetNamedFields(root, nsmgr, compilation, caches, typeCache, cancellationToken); var typeArguments = GetAttributeValue(root, "TypeArguments", XamlParser.X2006Uri, XamlParser.X2009Uri); - baseType = GetTypeName(new XmlType(root.NamespaceURI, root.LocalName, typeArguments != null ? TypeArgumentsParser.ParseExpression(typeArguments, nsmgr, null) : null), compilation, caches); + baseType = GetTypeName(new XmlType(root.NamespaceURI, root.LocalName, typeArguments != null ? TypeArgumentsParser.ParseExpression(typeArguments, nsmgr, null) : null), compilation, caches, typeCache); // x:ClassModifier attribute var classModifier = GetAttributeValue(root, "ClassModifier", XamlParser.X2006Uri, XamlParser.X2009Uri); @@ -345,7 +356,7 @@ static bool GetXamlCompilationProcessingInstruction(XmlDocument xmlDoc) return true; } - static IEnumerable<(string name, string type, string accessModifier)> GetNamedFields(XmlNode root, XmlNamespaceManager nsmgr, Compilation compilation, AssemblyCaches caches, CancellationToken cancellationToken) + static IEnumerable<(string name, string type, string accessModifier)> GetNamedFields(XmlNode root, XmlNamespaceManager nsmgr, Compilation compilation, AssemblyCaches caches, IDictionary typeCache, CancellationToken cancellationToken) { var xPrefix = nsmgr.LookupPrefix(XamlParser.X2006Uri) ?? nsmgr.LookupPrefix(XamlParser.X2009Uri); if (xPrefix == null) @@ -371,13 +382,17 @@ static bool GetXamlCompilationProcessingInstruction(XmlDocument xmlDoc) var accessModifier = fieldModifier?.ToLowerInvariant().Replace("notpublic", "internal") ?? "private"; //notpublic is WPF for internal if (!new[] { "private", "public", "internal", "protected" }.Contains(accessModifier)) //quick validation accessModifier = "private"; - yield return (name ?? "", GetTypeName(xmlType, compilation, caches), accessModifier); + yield return (name ?? "", GetTypeName(xmlType, compilation, caches, typeCache), accessModifier); } } - static string GetTypeName(XmlType xmlType, Compilation compilation, AssemblyCaches caches) + static string GetTypeName(XmlType xmlType, Compilation compilation, AssemblyCaches caches, IDictionary typeCache) { - string returnType; + if (typeCache.TryGetValue(xmlType, out string returnType)) + { + return returnType; + } + var ns = GetClrNamespace(xmlType.NamespaceUri); if (ns != null) returnType = $"{ns}.{xmlType.Name}"; @@ -388,9 +403,11 @@ static string GetTypeName(XmlType xmlType, Compilation compilation, AssemblyCach } if (xmlType.TypeArguments != null) - returnType = $"{returnType}<{string.Join(", ", xmlType.TypeArguments.Select(typeArg => GetTypeName(typeArg, compilation, caches)))}>"; + returnType = $"{returnType}<{string.Join(", ", xmlType.TypeArguments.Select(typeArg => GetTypeName(typeArg, compilation, caches, typeCache)))}>"; - return $"global::{returnType}"; + returnType = $"global::{returnType}"; + typeCache[xmlType] = returnType; + return returnType; } static string? GetClrNamespace(string namespaceuri) diff --git a/src/Controls/src/SourceGen/Properties/launchSettings.json b/src/Controls/src/SourceGen/Properties/launchSettings.json new file mode 100644 index 000000000000..2060188b3a7b --- /dev/null +++ b/src/Controls/src/SourceGen/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "SourceGen - Maui.Controls.Sample": { + "commandName": "DebugRoslynComponent", + "targetProject": "..\\..\\samples\\Controls.Sample\\Maui.Controls.Sample.csproj" + } + } +} \ No newline at end of file diff --git a/src/Controls/src/Xaml/XamlNode.cs b/src/Controls/src/Xaml/XamlNode.cs index 36dbe6fe6dbe..03c643012257 100644 --- a/src/Controls/src/Xaml/XamlNode.cs +++ b/src/Controls/src/Xaml/XamlNode.cs @@ -1,4 +1,5 @@ #nullable disable +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -53,6 +54,34 @@ public XmlType(string namespaceUri, string name, IList typeArguments) public string NamespaceUri { get; } public string Name { get; } public IList TypeArguments { get; } + + public override bool Equals(object obj) + { + if (obj is not XmlType other) + { + return false; + } + + return + NamespaceUri == other.NamespaceUri && + Name == other.Name && + (TypeArguments == null && other.TypeArguments == null || TypeArguments.SequenceEqual(other.TypeArguments)); + } + + public override int GetHashCode() + { + unchecked + { +#if NETSTANDARD2_0 + int hashCode = NamespaceUri.GetHashCode(); + hashCode = (hashCode * 397) ^ Name.GetHashCode(); +#else + int hashCode = NamespaceUri.GetHashCode(StringComparison.Ordinal); + hashCode = (hashCode * 397) ^ Name.GetHashCode(StringComparison.Ordinal); +#endif + return hashCode; + } + } } abstract class BaseNode : IXmlLineInfo, INode diff --git a/src/Controls/src/Xaml/XmlTypeXamlExtensions.cs b/src/Controls/src/Xaml/XmlTypeXamlExtensions.cs index b9321f4956c5..bc30f025d49b 100644 --- a/src/Controls/src/Xaml/XmlTypeXamlExtensions.cs +++ b/src/Controls/src/Xaml/XmlTypeXamlExtensions.cs @@ -60,7 +60,7 @@ static class XmlTypeXamlExtensions lookupAssemblies.Add(new XmlnsDefinitionAttribute(namespaceURI, ns) { AssemblyName = asmstring }); } - var lookupNames = new List(); + var lookupNames = new List(capacity: 2); if (elementName != "DataTemplate" && !elementName.EndsWith("Extension", StringComparison.Ordinal)) lookupNames.Add(elementName + "Extension"); lookupNames.Add(elementName);