diff --git a/components/AppServices/CommunityToolkit.AppServices.SourceGenerators/AppServiceGenerator.cs b/components/AppServices/CommunityToolkit.AppServices.SourceGenerators/AppServiceGenerator.cs index f85e2a73..9764606a 100644 --- a/components/AppServices/CommunityToolkit.AppServices.SourceGenerators/AppServiceGenerator.cs +++ b/components/AppServices/CommunityToolkit.AppServices.SourceGenerators/AppServiceGenerator.cs @@ -78,7 +78,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) }); // Gather all interfaces, and only enable this branch if the target is a UWP app (the host) - IncrementalValuesProvider<(HierarchyInfo Hierarchy, AppServiceInfo Info)> appServiceHostInfo = + IncrementalValuesProvider<(HierarchyInfo, AppServiceInfo)> appServiceHostInfo = context.ForAttributeWithMetadataNameAndOptions( "CommunityToolkit.AppServices.AppServiceAttribute", static (node, _) => node is InterfaceDeclarationSyntax, @@ -105,6 +105,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) token.ThrowIfCancellationRequested(); + // Gather all methods for the app service type ImmutableArray methods = MethodInfo.From(typeSymbol, token); token.ThrowIfCancellationRequested(); @@ -113,8 +114,47 @@ public void Initialize(IncrementalGeneratorInitializationContext context) }) .Where(static item => item.Hierarchy is not null); - // Produce the host type - context.RegisterSourceOutput(appServiceHostInfo, static (context, item) => + // Also gather all explicitly requested host implementation types + IncrementalValuesProvider<(HierarchyInfo, AppServiceInfo)> additionalAppServiceHostInfo = + context.ForAttributeWithMetadataNameAndOptions( + "CommunityToolkit.AppServices.GeneratedAppServiceHostAttribute", + static (node, _) => true, + static (context, token) => + { + // Only retrieve host info if the target is a UWP application + if (!Helpers.IsUwpTarget(context.SemanticModel.Compilation, context.GlobalOptions)) + { + return default; + } + + // Get the target interface + if (context.Attributes[0].ConstructorArguments is not [{ Kind: TypedConstantKind.Type, Value: INamedTypeSymbol appServiceType }]) + { + return default; + } + + // Check if the current interface is in fact an app service type + if (!appServiceType.TryGetAppServicesNameFromAttribute(out string? appServiceName)) + { + return default; + } + + token.ThrowIfCancellationRequested(); + + HierarchyInfo hierarchy = HierarchyInfo.From(appServiceType, appServiceType.Name.Substring(1)); + + token.ThrowIfCancellationRequested(); + + ImmutableArray methods = MethodInfo.From(appServiceType, token); + + token.ThrowIfCancellationRequested(); + + return (Hierarchy: hierarchy, new AppServiceInfo(methods, appServiceName, appServiceType.GetFullyQualifiedName())); + }) + .Where(static item => item.Hierarchy is not null); + + // Shared helper to emit all discovered types + static void GenerateAppServiceHostType(SourceProductionContext context, (HierarchyInfo Hierarchy, AppServiceInfo Info) item) { ConstructorDeclarationSyntax constructorSyntax = Host.GetConstructorSyntax(item.Hierarchy, item.Info); ImmutableArray methodDeclarations = Host.GetMethodDeclarationsSyntax(item.Info); @@ -126,6 +166,10 @@ public void Initialize(IncrementalGeneratorInitializationContext context) $"/// A generated host implementation for the interface."); context.AddSource($"{item.Hierarchy.FilenameHint}.g.cs", compilationUnit.GetText(Encoding.UTF8)); - }); + } + + // Produce the host types + context.RegisterSourceOutput(appServiceHostInfo, GenerateAppServiceHostType); + context.RegisterSourceOutput(additionalAppServiceHostInfo, GenerateAppServiceHostType); } } diff --git a/components/AppServices/src/GeneratedAppServiceHostAttribute.cs b/components/AppServices/src/GeneratedAppServiceHostAttribute.cs new file mode 100644 index 00000000..9b5d251a --- /dev/null +++ b/components/AppServices/src/GeneratedAppServiceHostAttribute.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace CommunityToolkit.AppServices; + +/// +/// An attribute that can be used to request the generator to emit a host implementation of a given app service. +/// +[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false, Inherited = false)] +public sealed class GeneratedAppServiceHostAttribute : Attribute +{ + /// + /// Creates a new instance with the specified parameters. + /// + /// The type of the app service. + public GeneratedAppServiceHostAttribute(Type appServiceType) + { + AppServiceType = appServiceType; + } + + /// + /// Gets the type of the app service. + /// + public Type AppServiceType { get; } +}