diff --git a/src/Avalonia.Base/Animation/Easings/Easing.cs b/src/Avalonia.Base/Animation/Easings/Easing.cs index 6dfc4c86b3..d4f817ccc3 100644 --- a/src/Avalonia.Base/Animation/Easings/Easing.cs +++ b/src/Avalonia.Base/Animation/Easings/Easing.cs @@ -37,14 +37,9 @@ namespace Avalonia.Animation.Easings return new SplineEasing(KeySpline.Parse(e, CultureInfo.InvariantCulture)); } - if (TryCreateEasingInstance(e, out var easing)) - { - return easing; - } - else - { - throw new FormatException($"Easing \"{e}\" was not found in {Namespace} namespace."); - } + return TryCreateEasingInstance(e, out var easing) + ? easing + : throw new FormatException($"Easing \"{e}\" was not found in {Namespace} namespace."); } } } diff --git a/src/Avalonia.SourceGenerator/IsExternalInit.cs b/src/Avalonia.SourceGenerator/IsExternalInit.cs new file mode 100644 index 0000000000..c6ddf762ad --- /dev/null +++ b/src/Avalonia.SourceGenerator/IsExternalInit.cs @@ -0,0 +1,14 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +namespace System.Runtime.CompilerServices +{ + /// + /// Reserved to be used by the compiler for tracking metadata. + /// This class should not be used by developers in source code. + /// + [ExcludeFromCodeCoverage, DebuggerNonUserCode] + internal static class IsExternalInit + { + } +} diff --git a/src/Avalonia.SourceGenerator/SubtypesFactoryGenerator.cs b/src/Avalonia.SourceGenerator/SubtypesFactoryGenerator.cs index 7bc240853e..6ba1f6ac18 100644 --- a/src/Avalonia.SourceGenerator/SubtypesFactoryGenerator.cs +++ b/src/Avalonia.SourceGenerator/SubtypesFactoryGenerator.cs @@ -1,140 +1,48 @@ -using System.Collections.Generic; +using System; +using System.Collections.Immutable; using System.Linq; +using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Avalonia.SourceGenerator { - internal class GenerateSubtypesSyntaxReceiver : ISyntaxReceiver + [Generator(LanguageNames.CSharp)] + public class SubtypesFactoryGenerator : IIncrementalGenerator { - public List<(MethodDeclarationSyntax, AttributeSyntax)> CandidateMethods { get; } = new(); - public List Types { get; } = new(); - - public void OnVisitSyntaxNode(SyntaxNode syntaxNode) - { - if (syntaxNode is MethodDeclarationSyntax declarationSyntax) - { - foreach (var attribute in declarationSyntax.AttributeLists.SelectMany(i => i.Attributes)) - { - CandidateMethods.Add((declarationSyntax, attribute)); - } - } - - if (syntaxNode is ClassDeclarationSyntax or StructDeclarationSyntax) - { - Types.Add(syntaxNode); - } - } - } - - [Generator] - internal class SubtypesFactoryGenerator : ISourceGenerator - { - private readonly GenerateSubtypesSyntaxReceiver _receiver = new(); + private record MethodTarget(IMethodSymbol Method, ITypeSymbol BaseType, string Namespace); private static readonly string s_attributeName = typeof(SubtypesFactoryAttribute).FullName; - public void Execute(GeneratorExecutionContext context) + private static bool IsSubtypeOf(ITypeSymbol type, ITypeSymbol baseType) { - var methods = new List<(IMethodSymbol, ITypeSymbol, string)>(); - - foreach (var (method, attribute) in _receiver.CandidateMethods) - { - var semanticModel = context.Compilation.GetSemanticModel(method.SyntaxTree); - var attributeTypeInfo = semanticModel.GetTypeInfo(attribute); - if (attributeTypeInfo.Type is null || - attributeTypeInfo.Type.ToString() != s_attributeName || - attribute.ArgumentList is null) - { - continue; - } - - var arguments = attribute.ArgumentList.Arguments; - if (arguments.Count != 2) - { - continue; - } - - if (arguments[0].Expression is not TypeOfExpressionSyntax typeOfExpr || - arguments[1].Expression is not LiteralExpressionSyntax and not IdentifierNameSyntax) - { - continue; - } - - var type = semanticModel.GetTypeInfo(typeOfExpr.Type); - var ns = semanticModel.GetConstantValue(arguments[1].Expression); - var methodDeclInfo = semanticModel.GetDeclaredSymbol(method); - - if (type.Type is not ITypeSymbol baseType || - ns.HasValue is false || - ns.Value is not string nsValue || - methodDeclInfo is not IMethodSymbol methodSymbol || - methodSymbol.Parameters.Length != 2 || - methodSymbol.Parameters[1].RefKind != RefKind.Out) - { - continue; - } - - methods.Add((methodSymbol, baseType, nsValue)); - } - - var types = new List(); - foreach (var type in _receiver.Types) - { - var semanticModel = context.Compilation.GetSemanticModel(type.SyntaxTree); - var decl = semanticModel.GetDeclaredSymbol(type); - if (decl is ITypeSymbol typeSymbol) - { - types.Add(typeSymbol); - } - } - - GenerateSubTypes(context, methods, types); + return type.BaseType is not null && (SymbolEqualityComparer.Default.Equals(type.BaseType, baseType) || IsSubtypeOf(type.BaseType, baseType)); } - private bool IsSubtypeOf(ITypeSymbol type, ITypeSymbol baseType) + private static void GenerateSubTypes(SourceProductionContext context, MethodTarget methodTarget, ImmutableArray types) { - if (type.BaseType is null) - { - return false; - } + var (method, baseType, @namespace) = methodTarget; + var candidateTypes = types.Where(i => IsSubtypeOf(i, baseType)).Where(i => $"{i.ContainingNamespace}.".StartsWith($"{@namespace}.")).ToArray(); + var type = method.ContainingType; + var isGeneric = type.TypeParameters.Length > 0; + var isClass = type.TypeKind == TypeKind.Class; - if (SymbolEqualityComparer.Default.Equals(type.BaseType, baseType)) + if (method.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() is not MethodDeclarationSyntax methodDecl) { - return true; + return; } - return IsSubtypeOf(type.BaseType, baseType); - } + var parameters = new SeparatedSyntaxList().AddRange(methodDecl.ParameterList.Parameters.Select(i => i.WithAttributeLists(new SyntaxList()))); - private void GenerateSubTypes( - GeneratorExecutionContext context, - List<(IMethodSymbol Method, ITypeSymbol BaseType, string Namespace)> methods, - List types) - { - foreach (var (method, baseType, @namespace) in methods) - { - var candidateTypes = types.Where(i => IsSubtypeOf(i, baseType)).Where(i => $"{i.ContainingNamespace}.".StartsWith($"{@namespace}.")).ToArray(); - var type = method.ContainingType; - var isGeneric = type.TypeParameters.Length > 0; - var isClass = type.TypeKind == TypeKind.Class; - - if (method.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() is not MethodDeclarationSyntax methodDecl) - { - continue; - } + var methodDeclText = methodDecl + .WithAttributeLists(new SyntaxList()) + .WithParameterList(methodDecl.ParameterList.WithParameters(parameters)) + .WithBody(null) + .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.None)) + .WithoutTrivia().ToString(); - var parameters = new SeparatedSyntaxList().AddRange(methodDecl.ParameterList.Parameters.Select(i => i.WithAttributeLists(new SyntaxList()))); - - var methodDeclText = methodDecl - .WithAttributeLists(new SyntaxList()) - .WithParameterList(methodDecl.ParameterList.WithParameters(parameters)) - .WithBody(null) - .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.None)) - .WithoutTrivia().ToString(); - - var typeDecl = $"partial {(isClass ? "class" : "struct")} {type.Name}{(isGeneric ? $"<{string.Join(", ", type.TypeParameters)}>" : "")}"; - var source = $@"using System; + var typeDecl = $"partial {(isClass ? "class" : "struct")} {type.Name}{(isGeneric ? $"<{string.Join(", ", type.TypeParameters)}>" : "")}"; + var source = $@"using System; using System.Collections.Generic; namespace {method.ContainingNamespace} @@ -155,13 +63,99 @@ namespace {method.ContainingNamespace} }} }}"; - context.AddSource($"{type}.{method.MetadataName}.gen.cs", source); + context.AddSource($"{type}.{method.MetadataName}.gen.cs", source); + } + + private static MethodTarget? PopulateMethodTargets(GeneratorSyntaxContext context, CancellationToken token) + { + token.ThrowIfCancellationRequested(); + if (context.Node is MethodDeclarationSyntax method) + { + var attributes = method.AttributeLists.SelectMany(i => i.Attributes); + var semanticModel = context.SemanticModel; + foreach (var attribute in attributes) + { + var attributeTypeInfo = semanticModel.GetTypeInfo(attribute); + if (attributeTypeInfo.Type is null || + attributeTypeInfo.Type.ToString() != s_attributeName || + attribute.ArgumentList is null) + { + continue; + } + + var arguments = attribute.ArgumentList.Arguments; + if (arguments.Count != 2) + { + continue; + } + + if (arguments[0].Expression is not TypeOfExpressionSyntax typeOfExpr || + arguments[1].Expression is not LiteralExpressionSyntax and not IdentifierNameSyntax) + { + continue; + } + + var type = semanticModel.GetTypeInfo(typeOfExpr.Type); + var ns = semanticModel.GetConstantValue(arguments[1].Expression); + var methodDeclInfo = semanticModel.GetDeclaredSymbol(method); + + if (type.Type is not ITypeSymbol baseType || + ns.HasValue is false || + ns.Value is not string nsValue || + methodDeclInfo is not IMethodSymbol methodSymbol || + methodSymbol.Parameters.Length != 2 || + methodSymbol.Parameters[1].RefKind != RefKind.Out) + { + continue; + } + + return new MethodTarget(methodSymbol, baseType, nsValue); + } } + + return null; } - public void Initialize(GeneratorInitializationContext context) + public void Initialize(IncrementalGeneratorInitializationContext context) { - context.RegisterForSyntaxNotifications(() => _receiver); + var typesProvider = context.SyntaxProvider.CreateSyntaxProvider( + static (syntaxNode, token) => + { + token.ThrowIfCancellationRequested(); + return syntaxNode is ClassDeclarationSyntax or StructDeclarationSyntax; + }, + static (syntaxContext, token) => + { + token.ThrowIfCancellationRequested(); + return syntaxContext.Node is ClassDeclarationSyntax or StructDeclarationSyntax && + syntaxContext.SemanticModel.GetDeclaredSymbol(syntaxContext.Node) is ITypeSymbol typeSymbol + ? typeSymbol : null; + }) + .SelectMany((type, token) => + { + token.ThrowIfCancellationRequested(); + return type is null ? Array.Empty() : new ITypeSymbol[] { type }; + }); + + var methodsProvider = context.SyntaxProvider.CreateSyntaxProvider( + static (syntaxNode, token) => + { + token.ThrowIfCancellationRequested(); + return syntaxNode is ClassDeclarationSyntax or StructDeclarationSyntax or MethodDeclarationSyntax { AttributeLists.Count: > 0 }; + }, PopulateMethodTargets) + .SelectMany((method, token) => + { + token.ThrowIfCancellationRequested(); + return method is null ? Array.Empty() : new MethodTarget[] { method }; + }); + + var generateContext = methodsProvider.Combine(typesProvider.Collect()); + + context.RegisterSourceOutput(generateContext, static (sourceContext, source) => + { + sourceContext.CancellationToken.ThrowIfCancellationRequested(); + GenerateSubTypes(sourceContext, source.Left, source.Right); + }); } } }