From 1d19a96bc676496733d258780543e3d348090059 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 12 Jul 2023 22:51:33 +0200 Subject: [PATCH 01/22] Create blank Extensions.DependencyInjection project --- .../OpenSolution.bat | 3 ++ .../samples/Assets/icon.png | Bin 0 -> 6192 bytes .../samples/Dependencies.props | 31 ++++++++++++++++++ ...ensions.DependencyInjection.Samples.csproj | 8 +++++ .../samples/Extensions.DependencyInjection.md | 21 ++++++++++++ ...lkit.Extensions.DependencyInjection.csproj | 15 +++++++++ .../src/MultiTarget.props | 9 +++++ ...nsions.DependencyInjection.Tests.projitems | 11 +++++++ ...xtensions.DependencyInjection.Tests.shproj | 13 ++++++++ 9 files changed, 111 insertions(+) create mode 100644 components/Extensions.DependencyInjection/OpenSolution.bat create mode 100644 components/Extensions.DependencyInjection/samples/Assets/icon.png create mode 100644 components/Extensions.DependencyInjection/samples/Dependencies.props create mode 100644 components/Extensions.DependencyInjection/samples/Extensions.DependencyInjection.Samples.csproj create mode 100644 components/Extensions.DependencyInjection/samples/Extensions.DependencyInjection.md create mode 100644 components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj create mode 100644 components/Extensions.DependencyInjection/src/MultiTarget.props create mode 100644 components/Extensions.DependencyInjection/tests/Extensions.DependencyInjection.Tests.projitems create mode 100644 components/Extensions.DependencyInjection/tests/Extensions.DependencyInjection.Tests.shproj diff --git a/components/Extensions.DependencyInjection/OpenSolution.bat b/components/Extensions.DependencyInjection/OpenSolution.bat new file mode 100644 index 00000000..814a56d4 --- /dev/null +++ b/components/Extensions.DependencyInjection/OpenSolution.bat @@ -0,0 +1,3 @@ +@ECHO OFF + +powershell ..\..\tooling\ProjectHeads\GenerateSingleSampleHeads.ps1 -componentPath %CD% %* \ No newline at end of file diff --git a/components/Extensions.DependencyInjection/samples/Assets/icon.png b/components/Extensions.DependencyInjection/samples/Assets/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5f574ceca233597ecc5d775fd2f516c0d9685cfa GIT binary patch literal 6192 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 O0000 + + + + + + + + + + + + + + + + + + + + + diff --git a/components/Extensions.DependencyInjection/samples/Extensions.DependencyInjection.Samples.csproj b/components/Extensions.DependencyInjection/samples/Extensions.DependencyInjection.Samples.csproj new file mode 100644 index 00000000..f00fb85b --- /dev/null +++ b/components/Extensions.DependencyInjection/samples/Extensions.DependencyInjection.Samples.csproj @@ -0,0 +1,8 @@ + + + Extensions.DependencyInjection + + + + + diff --git a/components/Extensions.DependencyInjection/samples/Extensions.DependencyInjection.md b/components/Extensions.DependencyInjection/samples/Extensions.DependencyInjection.md new file mode 100644 index 00000000..66ec39e4 --- /dev/null +++ b/components/Extensions.DependencyInjection/samples/Extensions.DependencyInjection.md @@ -0,0 +1,21 @@ +--- +title: Extensions.DependencyInjection +author: githubaccount +description: TODO: Your experiment's description here +keywords: Extensions.DependencyInjection, Control, Layout +dev_langs: + - csharp +category: Controls +subcategory: Layout +discussion-id: 0 +issue-id: 0 +icon: assets/icon.png +--- + + + + + + + + diff --git a/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj b/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj new file mode 100644 index 00000000..d5d96d5c --- /dev/null +++ b/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj @@ -0,0 +1,15 @@ + + + Extensions.DependencyInjection + This package contains Extensions.DependencyInjection. + 0.0.1 + CommunityToolkit.Extensions.DependencyInjection + + + + + + + + + diff --git a/components/Extensions.DependencyInjection/src/MultiTarget.props b/components/Extensions.DependencyInjection/src/MultiTarget.props new file mode 100644 index 00000000..99fc8d22 --- /dev/null +++ b/components/Extensions.DependencyInjection/src/MultiTarget.props @@ -0,0 +1,9 @@ + + + + netstandard; + + \ No newline at end of file diff --git a/components/Extensions.DependencyInjection/tests/Extensions.DependencyInjection.Tests.projitems b/components/Extensions.DependencyInjection/tests/Extensions.DependencyInjection.Tests.projitems new file mode 100644 index 00000000..2c3c178d --- /dev/null +++ b/components/Extensions.DependencyInjection/tests/Extensions.DependencyInjection.Tests.projitems @@ -0,0 +1,11 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 47BFA618-6EF6-426A-B7CB-D8F2CEA68302 + + + Extensions.DependencyInjectionExperiment.Tests + + \ No newline at end of file diff --git a/components/Extensions.DependencyInjection/tests/Extensions.DependencyInjection.Tests.shproj b/components/Extensions.DependencyInjection/tests/Extensions.DependencyInjection.Tests.shproj new file mode 100644 index 00000000..383a0f4b --- /dev/null +++ b/components/Extensions.DependencyInjection/tests/Extensions.DependencyInjection.Tests.shproj @@ -0,0 +1,13 @@ + + + + 47BFA618-6EF6-426A-B7CB-D8F2CEA68302 + 14.0 + + + + + + + + From e3099483933bba56b9ba6664e6f77b4cb4aab9ba Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 13 Jul 2023 13:11:28 +0200 Subject: [PATCH 02/22] Add .targets file, setup NuGet packaging --- ...lkit.Extensions.DependencyInjection.csproj | 18 ++- ...kit.Extensions.DependencyInjection.targets | 104 ++++++++++++++++++ 2 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.targets diff --git a/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj b/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj index d5d96d5c..4ed7a3bb 100644 --- a/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj +++ b/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj @@ -2,14 +2,28 @@ Extensions.DependencyInjection This package contains Extensions.DependencyInjection. + true 0.0.1 CommunityToolkit.Extensions.DependencyInjection + $(PackageIdPrefix).$(ToolkitComponentName) + + + + + + + + + + + + + - - + diff --git a/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.targets b/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.targets new file mode 100644 index 00000000..20ddd05d --- /dev/null +++ b/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.targets @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @(CommunityToolkitExtensionsDependencyInjectionCurrentCompilerAssemblyIdentity->'%(Version)') + + + true + + + + + + + + + + + + + + + + + true + + + + + + + + + + true + + + + + + From 35660a759cafc42a057c0cb67830e545e47199ba Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 13 Jul 2023 13:14:59 +0200 Subject: [PATCH 03/22] Add blank source generator project --- .../AnalyzerReleases.Shipped.md | 10 +++++++ .../AnalyzerReleases.Unshipped.md | 2 ++ ...ependencyInjection.SourceGenerators.csproj | 30 +++++++++++++++++++ ...lkit.Extensions.DependencyInjection.csproj | 5 ++++ 4 files changed, 47 insertions(+) create mode 100644 components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/AnalyzerReleases.Shipped.md create mode 100644 components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/AnalyzerReleases.Unshipped.md create mode 100644 components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.csproj diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/AnalyzerReleases.Shipped.md b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/AnalyzerReleases.Shipped.md new file mode 100644 index 00000000..774665a0 --- /dev/null +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/AnalyzerReleases.Shipped.md @@ -0,0 +1,10 @@ +; Shipped analyzer releases +; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md + +## Release 1.0 + +### New Rules + +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +TKEXDI0001 | ServiceProviderGenerator | Error | diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/AnalyzerReleases.Unshipped.md b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/AnalyzerReleases.Unshipped.md new file mode 100644 index 00000000..f2b7fad6 --- /dev/null +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/AnalyzerReleases.Unshipped.md @@ -0,0 +1,2 @@ +; Unshipped analyzer release +; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.csproj b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.csproj new file mode 100644 index 00000000..e80e6cf8 --- /dev/null +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.csproj @@ -0,0 +1,30 @@ + + + netstandard2.0 + false + true + + + $(NoWarn);CS8500 + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj b/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj index 4ed7a3bb..01fb9959 100644 --- a/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj +++ b/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj @@ -11,6 +11,11 @@ + + + + + From e0042523c7dfa747a3c053462ff7416f2ddd695d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 13 Jul 2023 13:17:43 +0200 Subject: [PATCH 04/22] Add [Singleton] and [Transient] attributes --- ...lkit.Extensions.DependencyInjection.csproj | 4 + .../src/SingletonAttribute.cs | 80 +++++++++++++++++++ .../src/TransientAttribute.cs | 45 +++++++++++ 3 files changed, 129 insertions(+) create mode 100644 components/Extensions.DependencyInjection/src/SingletonAttribute.cs create mode 100644 components/Extensions.DependencyInjection/src/TransientAttribute.cs diff --git a/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj b/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj index 01fb9959..2b400d25 100644 --- a/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj +++ b/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj @@ -11,6 +11,10 @@ + + + + diff --git a/components/Extensions.DependencyInjection/src/SingletonAttribute.cs b/components/Extensions.DependencyInjection/src/SingletonAttribute.cs new file mode 100644 index 00000000..f99f90b9 --- /dev/null +++ b/components/Extensions.DependencyInjection/src/SingletonAttribute.cs @@ -0,0 +1,80 @@ +// 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; +using System.Diagnostics; +using Microsoft.Extensions.DependencyInjection; + +namespace CommunityToolkit.Extensions.DependencyInjection; + +/// +/// +/// An attribute that can be used to instruct the generator to add a singleton service to the target instance. +/// +/// +/// This attribute should be added to a method receiving an +/// instance, and the generator will register all requested services (optionally also returning the input object). +/// +/// +/// That is, given a declaration as follows: +/// +/// [Singleton(typeof(MyServiceA), typeof(IMyServiceA))] +/// [Singleton(typeof(MyServiceB), typeof(IMyServiceB))] +/// [Singleton(typeof(MyServiceC), typeof(IMyServiceC))] +/// private static partial void ConfigureServices(IServiceCollection services); +/// +/// The generator will produce code as follows: +/// +/// private static partial void ConfigureServices(IServiceCollection services) +/// { +/// services.AddSingleton(typeof(IMyServiceA), static services => new MyServiceA()); +/// services.AddSingleton(typeof(IMyServiceB), static services => new MyServiceB( +/// services.GetRequiredServices<IMyServiceA>())); +/// services.AddSingleton(typeof(IMyServiceC), static services => new MyServiceC( +/// services.GetRequiredServices<IMyServiceA>(), +/// services.GetRequiredServices<IMyServiceB>())); +/// } +/// +/// +/// +/// +/// This attribute is conditional for two reasons: +/// +/// +/// Since the attributes are only used for source generation and there can be a large number of them, this +/// reduces the metadata impact on the final assemblies. If needed, the directive can be manually defined. +/// +/// +/// The attributes have a constructor parameter of an array type, which is not allowed in WinRT assemblies. +/// Making the attributes conditional makes Roslyn skip emitting them, which avoids WinMDExp generating an +/// invalid PE file and then causing projects referencing it to fail to build. For more info on the WinMDExp +/// issue, see . +/// +/// +/// +[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] +[Conditional("SERVICES_CONFIGURATION_METADATA")] +public sealed class SingletonAttribute : Attribute +{ + /// + /// Creates a new instance with the specified parameters. + /// + /// The implementation type for the service. + /// The service types to register for the provided implementation. + public SingletonAttribute(Type implementationType, params Type[] serviceTypes) + { + ImplementationType = implementationType; + ServiceTypes = serviceTypes; + } + + /// + /// Gets the implementation type for the service to register. + /// + public Type ImplementationType { get; } + + /// + /// Gets the supported service types for the implementation being registered. + /// + public Type[] ServiceTypes { get; } +} diff --git a/components/Extensions.DependencyInjection/src/TransientAttribute.cs b/components/Extensions.DependencyInjection/src/TransientAttribute.cs new file mode 100644 index 00000000..3a451e1d --- /dev/null +++ b/components/Extensions.DependencyInjection/src/TransientAttribute.cs @@ -0,0 +1,45 @@ +// 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; +using System.Diagnostics; +using Microsoft.Extensions.DependencyInjection; + +namespace CommunityToolkit.Extensions.DependencyInjection; + +/// +/// +/// An attribute that can be used to instruct the generator to add a transient service to the input instance. +/// +/// +/// This attribute can be used in the same way as , the only difference being that it will register transient services. +/// A method can be annotated with any combination of and . +/// +/// +/// For more info, see . +[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] +[Conditional("SERVICES_CONFIGURATION_METADATA")] +public sealed class TransientAttribute : Attribute +{ + /// + /// Creates a new instance with the specified parameters. + /// + /// The implementation type for the service. + /// The service types to register for the provided implementation. + public TransientAttribute(Type implementationType, params Type[] serviceTypes) + { + ImplementationType = implementationType; + ServiceTypes = serviceTypes; + } + + /// + /// Gets the implementation type for the service to register. + /// + public Type ImplementationType { get; } + + /// + /// Gets the supported service types for the implementation being registered. + /// + public Type[] ServiceTypes { get; } +} From d71823703138f98b55f4c107a24ef6172dd26d44 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 13 Jul 2023 13:21:28 +0200 Subject: [PATCH 05/22] Port source generator code from Store --- .../AnalyzerReleases.Shipped.md | 5 +- .../InvalidServiceRegistrationAnalyzer.cs | 151 ++++++++ .../Diagnostics/DiagnosticDescriptors.cs | 75 ++++ .../Extensions/AttributeDataExtensions.cs | 28 ++ .../Extensions/CompilationExtensions.cs | 50 +++ .../Extensions/ISymbolExtensions.cs | 23 ++ .../Extensions/ITypeSymbolExtensions.cs | 93 +++++ .../IncrementalValuesProviderExtensions.cs | 101 ++++++ .../Helpers/EquatableArray{T}.cs | 177 +++++++++ .../Helpers/ImmutableArrayBuilder{T}.cs | 223 ++++++++++++ .../Helpers/ObjectPool{T}.cs | 163 +++++++++ .../Models/HierarchyInfo.Syntax.cs | 92 +++++ .../Models/HierarchyInfo.cs | 58 +++ .../Models/RegisteredServiceInfo.cs | 20 + .../Models/ServiceCollectionInfo.cs | 14 + .../Models/ServiceProviderMethodInfo.cs | 22 ++ .../Models/ServiceRegistrationKind.cs | 21 ++ .../Models/TypeInfo.cs | 46 +++ .../ServiceProviderGenerator.Execute.cs | 343 ++++++++++++++++++ .../ServiceProviderGenerator.cs | 57 +++ 20 files changed, 1761 insertions(+), 1 deletion(-) create mode 100644 components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Diagnostics/Analyzers/InvalidServiceRegistrationAnalyzer.cs create mode 100644 components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs create mode 100644 components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Extensions/AttributeDataExtensions.cs create mode 100644 components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Extensions/CompilationExtensions.cs create mode 100644 components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Extensions/ISymbolExtensions.cs create mode 100644 components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Extensions/ITypeSymbolExtensions.cs create mode 100644 components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Extensions/IncrementalValuesProviderExtensions.cs create mode 100644 components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/EquatableArray{T}.cs create mode 100644 components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/ImmutableArrayBuilder{T}.cs create mode 100644 components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/ObjectPool{T}.cs create mode 100644 components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Models/HierarchyInfo.Syntax.cs create mode 100644 components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Models/HierarchyInfo.cs create mode 100644 components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Models/RegisteredServiceInfo.cs create mode 100644 components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Models/ServiceCollectionInfo.cs create mode 100644 components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Models/ServiceProviderMethodInfo.cs create mode 100644 components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Models/ServiceRegistrationKind.cs create mode 100644 components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Models/TypeInfo.cs create mode 100644 components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/ServiceProviderGenerator.Execute.cs create mode 100644 components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/ServiceProviderGenerator.cs diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/AnalyzerReleases.Shipped.md b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/AnalyzerReleases.Shipped.md index 774665a0..f0b8231e 100644 --- a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/AnalyzerReleases.Shipped.md +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/AnalyzerReleases.Shipped.md @@ -7,4 +7,7 @@ Rule ID | Category | Severity | Notes --------|----------|----------|------- -TKEXDI0001 | ServiceProviderGenerator | Error | +TKEXDI0001 | CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.InvalidServiceRegistrationAnalyzer | Error | +TKEXDI0002 | CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.InvalidServiceRegistrationAnalyzer | Error | +TKEXDI0003 | CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.InvalidServiceRegistrationAnalyzer | Error | +TKEXDI0004 | CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.InvalidServiceRegistrationAnalyzer | Warning | diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Diagnostics/Analyzers/InvalidServiceRegistrationAnalyzer.cs b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Diagnostics/Analyzers/InvalidServiceRegistrationAnalyzer.cs new file mode 100644 index 00000000..2e428822 --- /dev/null +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Diagnostics/Analyzers/InvalidServiceRegistrationAnalyzer.cs @@ -0,0 +1,151 @@ +// Licensnsed 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.Collections.Generic; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Extensions; +using static CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Diagnostics.DiagnosticDescriptors; + +namespace CommunityToolkit.Extensions.DependencyInjection.SourceGenerators; + +/// +/// A diagnostic analyzer that emits diagnostics for invalid service registrations. +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class InvalidServiceRegistrationAnalyzer : DiagnosticAnalyzer +{ + /// + /// The mapping of target attributes that will trigger the analyzer. + /// + private static readonly ImmutableDictionary RegistrationAttributeNamesToFullyQualifiedNamesMap = ImmutableDictionary.CreateRange(new[] + { + new KeyValuePair("SingletonAttribute", "CommunityToolkit.Extensions.DependencyInjection.SingletonAttribute"), + new KeyValuePair("TransientAttribute", "CommunityToolkit.Extensions.DependencyInjection.TransientAttribute"), + }); + + /// + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create( + InvalidRegistrationImplementationType, + InvalidRegistrationServiceType, + DuplicateImplementationTypeRegistration, + DuplicateServiceTypeRegistration); + + /// + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); + context.EnableConcurrentExecution(); + + context.RegisterCompilationStartAction(static context => + { + // Try to get all necessary type symbols + if (!context.Compilation.TryBuildNamedTypeSymbolMap(RegistrationAttributeNamesToFullyQualifiedNamesMap, out ImmutableDictionary? typeSymbols)) + { + return; + } + + // Register a callback for all methpds + context.RegisterSymbolAction(context => + { + IMethodSymbol methodSymbol = (IMethodSymbol)context.Symbol; + + HashSet implementationTypeRegistrations = new(SymbolEqualityComparer.Default); + HashSet serviceTypeRegistrations = new(SymbolEqualityComparer.Default); + + foreach (AttributeData attributeData in context.Symbol.GetAttributes()) + { + // Go over each attribute on the target method and find the ones indicating a service registration + if (attributeData.AttributeClass is { Name: string attributeName } attributeClass && + typeSymbols.TryGetValue(attributeName, out INamedTypeSymbol? attributeSymbol) && + SymbolEqualityComparer.Default.Equals(attributeClass, attributeSymbol)) + { + // Ensure the attribute arguments are present, and retrieve them + if (attributeData.ConstructorArguments is not [ + { Kind: TypedConstantKind.Type, Value: ITypeSymbol implementationType }, + { Kind: TypedConstantKind.Array, Values: ImmutableArray serviceTypes }]) + { + continue; + } + + // Check the implementation type is valid (note: not checking constructors just yet) + if (implementationType is not INamedTypeSymbol + { + TypeKind: TypeKind.Class, + IsStatic: false, + IsAbstract: false, + InstanceConstructors: [IMethodSymbol, ..] constructors + }) + { + // The type is not valid, emit a diagnostic + context.ReportDiagnostic(Diagnostic.Create( + InvalidRegistrationImplementationType, + attributeData.GetLocation(), + implementationType)); + } + + // Check if the implementation type has not been seen before + if (!implementationTypeRegistrations.Add(implementationType)) + { + context.ReportDiagnostic(Diagnostic.Create( + DuplicateImplementationTypeRegistration, + attributeData.GetLocation(), + implementationType)); + } + + // If no service types are present, the service is registered as the implementation type + if (serviceTypes.IsEmpty) + { + // Since we're tracking all registered service types, we need to track that case as well. + // This covers cases such as: + // + // [Singleton(typeof(A))] + // [Singleton(typeof(BDerivesFromA), typeof(A), typeof(IB))] + // + // That is, the first attribute will trigger this code path and the implementation type + // will be registered, and the second attribute will go through the explicit list of + // service types to register, see A being present again, and correctly emit the diagnostic. + if (!serviceTypeRegistrations.Add(implementationType)) + { + context.ReportDiagnostic(Diagnostic.Create( + DuplicateServiceTypeRegistration, + attributeData.GetLocation(), + implementationType)); + } + } + else + { + // Go over all declared service types and validate them as well + foreach (TypedConstant serviceType in serviceTypes) + { + // For a service type to be valid, there has to be an implicit or identity conversion between that and the service type + if (serviceType.Value is not INamedTypeSymbol { TypeKind: TypeKind.Class or TypeKind.Interface, IsStatic: false } targetServiceType || + context.Compilation.ClassifyCommonConversion(implementationType, targetServiceType) is not ({ IsIdentity: true } or { IsImplicit: true })) + { + // The service type is not valid, emit a diagnostic + context.ReportDiagnostic(Diagnostic.Create( + InvalidRegistrationServiceType, + attributeData.GetLocation(), + implementationType, + serviceType.Value)); + } + + // Check if the service type has not been seen before + if (serviceType.Value is ITypeSymbol typeSymbol && + !serviceTypeRegistrations.Add(typeSymbol)) + { + context.ReportDiagnostic(Diagnostic.Create( + DuplicateServiceTypeRegistration, + attributeData.GetLocation(), + typeSymbol)); + } + } + } + } + } + }, SymbolKind.Method); + }); + } +} diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs new file mode 100644 index 00000000..6702e611 --- /dev/null +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs @@ -0,0 +1,75 @@ +// Licensnsed 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 Microsoft.CodeAnalysis; + +#pragma warning disable IDE0090 // Use 'new(...)' for field initializers, suppressed as it breaks a Roslyn analyzer + +namespace CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Diagnostics; + +/// +/// A container for all instances for errors reported by analyzers in this project. +/// +internal static class DiagnosticDescriptors +{ + /// + /// Gets a indicating when a registered service is using an invalid implementation type. + /// + /// Format: "Cannot register a service of implementation type {0}, as the type has to be a non static, non abstract class with a public constructor". + /// + /// + public static readonly DiagnosticDescriptor InvalidRegistrationImplementationType = new DiagnosticDescriptor( + id: "SRVCNFG0001", + title: "Invalid registration implementation type", + messageFormat: "Cannot register a service of implementation type {0}, as the type has to be a non static, non abstract class with a public constructor", + category: typeof(InvalidServiceRegistrationAnalyzer).FullName, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "Registered service implementation types must be non static, non abstract classes with a public constructor."); + + /// + /// Gets a indicating when a registered service is using an invalid service type. + /// + /// Format: "Cannot register a service of implementation type {0} with the type {1}, as there is no implicit type conversion between the two". + /// + /// + public static readonly DiagnosticDescriptor InvalidRegistrationServiceType = new DiagnosticDescriptor( + id: "SRVCNFG0002", + title: "Invalid registration service type", + messageFormat: "Cannot register a service of implementation type {0} with the type {1}, as there is no implicit type conversion between the two", + category: typeof(InvalidServiceRegistrationAnalyzer).FullName, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "Registered service types must be implicitly convertible from their implementation type."); + + /// + /// Gets a indicating when an implementation type is registered twice. + /// + /// Format: "The implementation type {0} has already been registered on the target service collection". + /// + /// + public static readonly DiagnosticDescriptor DuplicateImplementationTypeRegistration = new DiagnosticDescriptor( + id: "SRVCNFG0003", + title: "Duplicate implementation type registration", + messageFormat: "The implementation type {0} has already been registered on the target service collection", + category: typeof(InvalidServiceRegistrationAnalyzer).FullName, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "Each implementation type can only be registered once in a target service collection."); + + /// + /// Gets a indicating when a service type is registered twice. + /// + /// Format: "The service type {0} has already been registered on the target service collection". + /// + /// + public static readonly DiagnosticDescriptor DuplicateServiceTypeRegistration = new DiagnosticDescriptor( + id: "SRVCNFG0004", + title: "Duplicate service type registration", + messageFormat: "The service type {0} has already been registered on the target service collection", + category: typeof(InvalidServiceRegistrationAnalyzer).FullName, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: "Each service type should only be registered once in a target service collection."); +} diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Extensions/AttributeDataExtensions.cs b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Extensions/AttributeDataExtensions.cs new file mode 100644 index 00000000..24af1e45 --- /dev/null +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Extensions/AttributeDataExtensions.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 Microsoft.CodeAnalysis; + +namespace CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Extensions; + +/// +/// Extension methods for the type. +/// +internal static class AttributeDataExtensions +{ + /// + /// Tries to get the location of the input instance. + /// + /// The input instance to get the location for. + /// The resulting location for , if a syntax reference is available. + public static Location? GetLocation(this AttributeData attributeData) + { + if (attributeData.ApplicationSyntaxReference is { } syntaxReference) + { + return syntaxReference.SyntaxTree.GetLocation(syntaxReference.Span); + } + + return null; + } +} \ No newline at end of file diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Extensions/CompilationExtensions.cs b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Extensions/CompilationExtensions.cs new file mode 100644 index 00000000..9f497f85 --- /dev/null +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Extensions/CompilationExtensions.cs @@ -0,0 +1,50 @@ +// 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; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; + +namespace CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Extensions; + +/// +/// Extension methods for the type. +/// +internal static class CompilationExtensions +{ + /// + /// Tries to build a map of instances form the input mapping of names. + /// + /// The type of keys for each symbol. + /// The to consider for analysis. + /// The input mapping of keys to fully qualified type names. + /// The resulting mapping of keys to resolved instances. + /// Whether all requested instances could be resolved. + public static bool TryBuildNamedTypeSymbolMap( + this Compilation compilation, + IEnumerable> typeNames, + [NotNullWhen(true)] out ImmutableDictionary? typeSymbols) + where T : IEquatable + { + ImmutableDictionary.Builder builder = ImmutableDictionary.CreateBuilder(); + + foreach (KeyValuePair pair in typeNames) + { + if (compilation.GetTypeByMetadataName(pair.Value) is not INamedTypeSymbol attributeSymbol) + { + typeSymbols = null; + + return false; + } + + builder.Add(pair.Key, attributeSymbol); + } + + typeSymbols = builder.ToImmutable(); + + return true; + } +} diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Extensions/ISymbolExtensions.cs b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Extensions/ISymbolExtensions.cs new file mode 100644 index 00000000..b381115f --- /dev/null +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Extensions/ISymbolExtensions.cs @@ -0,0 +1,23 @@ +// 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 Microsoft.CodeAnalysis; + +namespace CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Extensions; + +/// +/// Extension methods for the type. +/// +internal static class ISymbolExtensions +{ + /// + /// Gets the fully qualified name for a given symbol. + /// + /// The input instance. + /// The fully qualified name for . + public static string GetFullyQualifiedName(this ISymbol symbol) + { + return symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + } +} \ No newline at end of file diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Extensions/ITypeSymbolExtensions.cs b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Extensions/ITypeSymbolExtensions.cs new file mode 100644 index 00000000..f061ade5 --- /dev/null +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Extensions/ITypeSymbolExtensions.cs @@ -0,0 +1,93 @@ +// 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; +using Microsoft.CodeAnalysis; +using CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Helpers; + +namespace CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Extensions; + +/// +/// Extension methods for the type. +/// +internal static class ITypeSymbolExtensions +{ + /// + /// Checks whether or not a given type symbol has a specified fully qualified metadata name. + /// + /// The input instance to check. + /// The full name to check. + /// Whether has a full name equals to . + public static bool HasFullyQualifiedMetadataName(this ITypeSymbol symbol, string name) + { + using ImmutableArrayBuilder builder = ImmutableArrayBuilder.Rent(); + + symbol.AppendFullyQualifiedMetadataName(in builder); + + return builder.Span.SequenceEqual(name.AsSpan()); + } + + /// + /// Gets the fully qualified metadata name for a given instance. + /// + /// The input instance. + /// The fully qualified metadata name for . + public static string GetFullyQualifiedMetadataName(this ITypeSymbol symbol) + { + using ImmutableArrayBuilder builder = ImmutableArrayBuilder.Rent(); + + symbol.AppendFullyQualifiedMetadataName(in builder); + + return builder.ToString(); + } + + /// + /// Appends the fully qualified metadata name for a given symbol to a target builder. + /// + /// The input instance. + /// The target instance. + private static void AppendFullyQualifiedMetadataName(this ITypeSymbol symbol, in ImmutableArrayBuilder builder) + { + static void BuildFrom(ISymbol? symbol, in ImmutableArrayBuilder builder) + { + switch (symbol) + { + // Namespaces that are nested also append a leading '.' + case INamespaceSymbol { ContainingNamespace.IsGlobalNamespace: false }: + BuildFrom(symbol.ContainingNamespace, in builder); + builder.Add('.'); + builder.AddRange(symbol.MetadataName.AsSpan()); + break; + + // Other namespaces (ie. the one right before global) skip the leading '.' + case INamespaceSymbol { IsGlobalNamespace: false }: + builder.AddRange(symbol.MetadataName.AsSpan()); + break; + + // Types with no namespace just have their metadata name directly written + case ITypeSymbol { ContainingSymbol: INamespaceSymbol { IsGlobalNamespace: true } }: + builder.AddRange(symbol.MetadataName.AsSpan()); + break; + + // Types with a containing non-global namespace also append a leading '.' + case ITypeSymbol { ContainingSymbol: INamespaceSymbol namespaceSymbol }: + BuildFrom(namespaceSymbol, in builder); + builder.Add('.'); + builder.AddRange(symbol.MetadataName.AsSpan()); + break; + + // Nested types append a leading '+' + case ITypeSymbol { ContainingSymbol: ITypeSymbol typeSymbol }: + BuildFrom(typeSymbol, in builder); + builder.Add('+'); + builder.AddRange(symbol.MetadataName.AsSpan()); + break; + default: + break; + } + } + + BuildFrom(symbol, in builder); + } +} \ No newline at end of file diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Extensions/IncrementalValuesProviderExtensions.cs b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Extensions/IncrementalValuesProviderExtensions.cs new file mode 100644 index 00000000..dda7baa1 --- /dev/null +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Extensions/IncrementalValuesProviderExtensions.cs @@ -0,0 +1,101 @@ +// 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.Collections.Generic; +using System.Collections.Immutable; +using System; +using Microsoft.CodeAnalysis; +using CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Helpers; + +namespace CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Extensions; + +/// +/// Extension methods for the type. +/// +internal static class IncrementalValuesProviderExtensions +{ + /// + /// Concatenates two sources into one. + /// + /// The type of items to combine. + /// The first source. + /// The second source. + /// The resulting sequence combining items from both and then . + public static IncrementalValuesProvider Concat(this IncrementalValuesProvider left, IncrementalValuesProvider right) + { + IncrementalValueProvider> leftItems = left.Collect(); + IncrementalValueProvider> rightItems = right.Collect(); + IncrementalValueProvider<(ImmutableArray Left, ImmutableArray Right)> allItems = leftItems.Combine(rightItems); + + return allItems.SelectMany((item, token) => + { + ImmutableArray.Builder builder = ImmutableArray.CreateBuilder(item.Left.Length + item.Right.Length); + + builder.AddRange(item.Left); + builder.AddRange(item.Right); + + return builder.MoveToImmutable(); + }); + } + + /// + /// Groups items in a given sequence by a specified key. + /// + /// The type of items in the source. + /// The type of resulting key elements. + /// The type of resulting projected elements. + /// The input instance. + /// The key selection . + /// An with the grouped results. + public static IncrementalValuesProvider GroupBy( + this IncrementalValuesProvider source, + Func keySelector, + Func> elementsSelector, + Func, T> resultSelector) + where T : IEquatable + where TKey : IEquatable + where TElement : IEquatable + { + return source.Collect().SelectMany((item, token) => + { + Dictionary> map = new(); + + // For each input item, extract the key and the items and group them + foreach (T entry in item) + { + TKey key = keySelector(entry); + ImmutableArray items = elementsSelector(entry); + + // Get or create a builder backed by a pooled array + if (!map.TryGetValue(key, out ImmutableArrayBuilder builder)) + { + builder = ImmutableArrayBuilder.Rent(); + + map.Add(key, builder); + } + + // Aggregate all items for the current key + builder.AddRange(items.AsSpan()); + } + + token.ThrowIfCancellationRequested(); + + ImmutableArray.Builder result = ImmutableArray.CreateBuilder(map.Count); + + // For each grouped pair, create the resulting item + foreach (KeyValuePair> entry in map) + { + // Copy the aggregated items to a new array + ImmutableArray elements = entry.Value.ToImmutable(); + + // Manually dispose the rented buffer to return the array to the pool + entry.Value.Dispose(); + + result.Add(resultSelector(entry.Key, elements)); + } + + return result; + }); + } +} \ No newline at end of file diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/EquatableArray{T}.cs b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/EquatableArray{T}.cs new file mode 100644 index 00000000..e28fe636 --- /dev/null +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/EquatableArray{T}.cs @@ -0,0 +1,177 @@ +// 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. + +// This file is ported and adapted from ComputeSharp (Sergio0694/ComputeSharp), +// more info in ThirdPartyNotices.txt in the root of the project. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Helpers; + +/// +/// An imutable, equatable array. This is equivalent to but with value equality support. +/// +/// The type of values in the array. +internal readonly struct EquatableArray : IEquatable>, IEnumerable + where T : IEquatable +{ + /// + /// The underlying array. + /// + private readonly T[]? array; + + /// + /// Creates a new instance. + /// + /// The input to wrap. + public EquatableArray(ImmutableArray array) + { + this.array = Unsafe.As, T[]?>(ref array); + } + + /// + /// Gets a reference to an item at a specified position within the array. + /// + /// The index of the item to retrieve a reference to. + /// A reference to an item at a specified position within the array. + public T this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => AsImmutableArray()[index]; + } + + /// + /// Gets a value indicating whether the current array is empty. + /// + public bool IsEmpty + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => AsImmutableArray().IsEmpty; + } + + /// + public bool Equals(EquatableArray array) + { + return AsSpan().SequenceEqual(array.AsSpan()); + } + + /// + public override bool Equals(object? obj) + { + return obj is EquatableArray array && Equals(this, array); + } + + /// + public override unsafe int GetHashCode() + { + if (this.array is not T[] array) + { + return 0; + } + + int hashCode = 0; + + foreach (T value in array) + { + hashCode = unchecked((hashCode * (int)0xA5555529) + value.GetHashCode()); + } + + return hashCode; + } + + /// + /// Gets an instance from the current . + /// + /// The from the current . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ImmutableArray AsImmutableArray() + { + return Unsafe.As>(ref Unsafe.AsRef(in this.array)); + } + + /// + /// Creates an instance from a given . + /// + /// The input instance. + /// An instance from a given . + public static EquatableArray FromImmutableArray(ImmutableArray array) + { + return new(array); + } + + /// + /// Returns a wrapping the current items. + /// + /// A wrapping the current items. + public ReadOnlySpan AsSpan() + { + return AsImmutableArray().AsSpan(); + } + + /// + /// Gets an value to traverse items in the current array. + /// + /// An value to traverse items in the current array. + public ImmutableArray.Enumerator GetEnumerator() + { + return AsImmutableArray().GetEnumerator(); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)AsImmutableArray()).GetEnumerator(); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)AsImmutableArray()).GetEnumerator(); + } + + /// + /// Implicitly converts an to . + /// + /// An instance from a given . + public static implicit operator EquatableArray(ImmutableArray array) + { + return FromImmutableArray(array); + } + + /// + /// Implicitly converts an to . + /// + /// An instance from a given . + public static implicit operator ImmutableArray(EquatableArray array) + { + return array.AsImmutableArray(); + } + + /// + /// Checks whether two values are the same. + /// + /// The first value. + /// The second value. + /// Whether and are equal. + public static bool operator ==(EquatableArray left, EquatableArray right) + { + return left.Equals(right); + } + + /// + /// Checks whether two values are not the same. + /// + /// The first value. + /// The second value. + /// Whether and are not equal. + public static bool operator !=(EquatableArray left, EquatableArray right) + { + return !left.Equals(right); + } +} \ No newline at end of file diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/ImmutableArrayBuilder{T}.cs b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/ImmutableArrayBuilder{T}.cs new file mode 100644 index 00000000..d1cb7161 --- /dev/null +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/ImmutableArrayBuilder{T}.cs @@ -0,0 +1,223 @@ +// 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. + +// This file is ported and adapted from ComputeSharp (Sergio0694/ComputeSharp), +// more info in ThirdPartyNotices.txt in the root of the project. + +using System; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Helpers; + +/// +/// A helper type to build sequences of values with pooled buffers. +/// +/// The type of items to create sequences for. +internal struct ImmutableArrayBuilder : IDisposable +{ + /// + /// The shared instance to share objects. + /// + private static readonly ObjectPool SharedObjectPool = new(static () => new Writer()); + + /// + /// The rented instance to use. + /// + private Writer? writer; + + /// + /// Creates a value with a pooled underlying data writer. + /// + /// A instance to write data to. + public static ImmutableArrayBuilder Rent() + { + return new(SharedObjectPool.Allocate()); + } + + /// + /// Creates a new object with the specified parameters. + /// + /// The target data writer to use. + private ImmutableArrayBuilder(Writer writer) + { + this.writer = writer; + } + + /// + /// Gets the data written to the underlying buffer so far, as a . + /// + [UnscopedRef] + public readonly Span Span + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.writer!.Span; + } + + /// + public readonly void Add(T item) + { + this.writer!.Add(item); + } + + /// + /// Adds the specified items to the end of the array. + /// + /// The items to add at the end of the array. + public readonly void AddRange(ReadOnlySpan items) + { + this.writer!.AddRange(items); + } + + /// + public readonly ImmutableArray ToImmutable() + { + T[] array = this.writer!.Span.ToArray(); + + return Unsafe.As>(ref array); + } + + /// + public readonly T[] ToArray() + { + return this.writer!.Span.ToArray(); + } + + /// + public override readonly string ToString() + { + return this.writer!.Span.ToString(); + } + + /// + public void Dispose() + { + Writer? writer = this.writer; + + this.writer = null; + + if (writer is not null) + { + writer.Clear(); + + SharedObjectPool.Free(writer); + } + } + + /// + /// A class handling the actual buffer writing. + /// + private sealed class Writer + { + /// + /// The underlying array. + /// + private T[] array; + + /// + /// The starting offset within . + /// + private int index; + + /// + /// Creates a new instance with the specified parameters. + /// + public Writer() + { + if (typeof(T) == typeof(char)) + { + this.array = new T[1024]; + } + else + { + this.array = new T[8]; + } + + this.index = 0; + } + + /// + public Span Span + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(this.array, 0, this.index); + } + + /// + public void Add(T value) + { + EnsureCapacity(1); + + this.array[this.index++] = value; + } + + /// + public void AddRange(ReadOnlySpan items) + { + EnsureCapacity(items.Length); + + items.CopyTo(this.array.AsSpan(this.index)); + + this.index += items.Length; + } + + /// + /// Clears the items in the current writer. + /// + public void Clear() + { + if (typeof(T) != typeof(char)) + { + this.array.AsSpan(0, this.index).Clear(); + } + + this.index = 0; + } + + /// + /// Ensures that has enough free space to contain a given number of new items. + /// + /// The minimum number of items to ensure space for in . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void EnsureCapacity(int requestedSize) + { + if (requestedSize > this.array.Length - this.index) + { + ResizeBuffer(requestedSize); + } + } + + /// + /// Resizes to ensure it can fit the specified number of new items. + /// + /// The minimum number of items to ensure space for in . + [MethodImpl(MethodImplOptions.NoInlining)] + private void ResizeBuffer(int sizeHint) + { + int minimumSize = this.index + sizeHint; + int requestedSize = Math.Max(this.array.Length * 2, minimumSize); + + T[] newArray = new T[requestedSize]; + + Array.Copy(this.array, newArray, this.index); + + this.array = newArray; + } + } +} + +/// +/// Private helpers for the type. +/// +internal static class ImmutableArrayBuilder +{ + /// + /// Throws an for "index". + /// + public static void ThrowArgumentOutOfRangeExceptionForIndex() + { + throw new ArgumentOutOfRangeException("index"); + } +} \ No newline at end of file diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/ObjectPool{T}.cs b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/ObjectPool{T}.cs new file mode 100644 index 00000000..eca26ba1 --- /dev/null +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/ObjectPool{T}.cs @@ -0,0 +1,163 @@ +// 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. + +// Ported from Roslyn, see: https://github.com/dotnet/roslyn/blob/main/src/Dependencies/PooledObjects/ObjectPool%601.cs. + +using System; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Helpers; + +/// +/// +/// Generic implementation of object pooling pattern with predefined pool size limit. The main purpose +/// is that limited number of frequently used objects can be kept in the pool for further recycling. +/// +/// +/// Notes: +/// +/// +/// It is not the goal to keep all returned objects. Pool is not meant for storage. If there +/// is no space in the pool, extra returned objects will be dropped. +/// +/// +/// It is implied that if object was obtained from a pool, the caller will return it back in +/// a relatively short time. Keeping checked out objects for long durations is ok, but +/// reduces usefulness of pooling. Just new up your own. +/// +/// +/// +/// +/// Not returning objects to the pool in not detrimental to the pool's work, but is a bad practice. +/// Rationale: if there is no intent for reusing the object, do not use pool - just use "new". +/// +/// +/// The type of objects to pool. +internal sealed class ObjectPool + where T : class +{ + /// + /// The factory is stored for the lifetime of the pool. We will call this only when pool needs to + /// expand. compared to "new T()", Func gives more flexibility to implementers and faster than "new T()". + /// + private readonly Func factory; + + /// + /// The array of cached items. + /// + private readonly Element[] items; + + /// + /// Storage for the pool objects. The first item is stored in a dedicated field + /// because we expect to be able to satisfy most requests from it. + /// + private T? firstItem; + + /// + /// Creates a new instance with the specified parameters. + /// + /// The input factory to produce items. + public ObjectPool(Func factory) + : this(factory, Environment.ProcessorCount * 2) + { + } + + /// + /// Creates a new instance with the specified parameters. + /// + /// The input factory to produce items. + /// The pool size to use. + public ObjectPool(Func factory, int size) + { + this.factory = factory; + this.items = new Element[size - 1]; + } + + /// + /// Produces a instance. + /// + /// The returned item to use. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T Allocate() + { + T? item = this.firstItem; + + if (item is null || item != Interlocked.CompareExchange(ref this.firstItem, null, item)) + { + item = AllocateSlow(); + } + + return item; + } + + /// + /// Returns a given instance to the pool. + /// + /// The instance to return. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Free(T obj) + { + if (this.firstItem is null) + { + this.firstItem = obj; + } + else + { + FreeSlow(obj); + } + } + + /// + /// Allocates a new item. + /// + /// The returned item to use. + [MethodImpl(MethodImplOptions.NoInlining)] + private T AllocateSlow() + { + foreach (ref Element element in this.items.AsSpan()) + { + T? instance = element.Value; + + if (instance is not null) + { + if (instance == Interlocked.CompareExchange(ref element.Value, null, instance)) + { + return instance; + } + } + } + + return this.factory(); + } + + /// + /// Frees a given item. + /// + /// The item to return to the pool. + [MethodImpl(MethodImplOptions.NoInlining)] + private void FreeSlow(T obj) + { + foreach (ref Element element in this.items.AsSpan()) + { + if (element.Value is null) + { + element.Value = obj; + + break; + } + } + } + + /// + /// A container for a produced item (using a wrapper to avoid covariance checks). + /// + private struct Element + { + /// + /// The value held at the current element. + /// + internal T? Value; + } +} \ No newline at end of file diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Models/HierarchyInfo.Syntax.cs b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Models/HierarchyInfo.Syntax.cs new file mode 100644 index 00000000..ba1d7282 --- /dev/null +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Models/HierarchyInfo.Syntax.cs @@ -0,0 +1,92 @@ +// 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. + +// This file is ported and adapted from ComputeSharp (Sergio0694/ComputeSharp), +// more info in ThirdPartyNotices.txt in the root of the project. + +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; + +namespace CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Models; + +/// +partial record HierarchyInfo +{ + /// + /// Creates a instance wrapping the given members. + /// + /// The input instances to use. + /// A object wrapping . + public CompilationUnitSyntax GetCompilationUnit(ImmutableArray memberDeclarations) + { + SyntaxToken[] typeModifierTokens = Modifiers.AsImmutableArray().Select(static m => Token((SyntaxKind)m)).ToArray(); + + // Create the partial type declaration with the given member declarations. + // This code produces a class declaration as follows: + // + // TYPE_NAME> + // { + // + // } + TypeDeclarationSyntax typeDeclarationSyntax = + Hierarchy[0].GetSyntax() + .AddModifiers(typeModifierTokens) + .AddMembers(memberDeclarations.ToArray()) + .WithLeadingTrivia(Comment("/// ")); + + // Add all parent types in ascending order, if any + foreach (TypeInfo parentType in Hierarchy.AsSpan().Slice(1)) + { + typeDeclarationSyntax = + parentType.GetSyntax() + .AddModifiers(Token(SyntaxKind.PartialKeyword)) + .AddMembers(typeDeclarationSyntax) + .WithLeadingTrivia(Comment("/// ")); + } + + // Prepare the leading trivia for the generated compilation unit. + // This will produce code as follows: + // + // + // #pragma warning disable + // #nullable enable + SyntaxTriviaList syntaxTriviaList = TriviaList( + Comment("// "), + Trivia(PragmaWarningDirectiveTrivia(Token(SyntaxKind.DisableKeyword), true)), + Trivia(NullableDirectiveTrivia(Token(SyntaxKind.EnableKeyword), true)), + Comment(" ")); + + if (Namespace is "") + { + // If there is no namespace, attach the pragma directly to the declared type, + // and skip the namespace declaration. This will produce code as follows: + // + // + // + return + CompilationUnit() + .AddMembers(typeDeclarationSyntax.WithLeadingTrivia(syntaxTriviaList.Add(Comment(" ")))) + .NormalizeWhitespace(); + } + + // Create the compilation unit with disabled warnings, target namespace and generated type. + // This will produce code as follows: + // + // + // namespace + // { + // + // } + return + CompilationUnit().AddMembers( + NamespaceDeclaration(IdentifierName(Namespace)) + .WithLeadingTrivia(syntaxTriviaList) + .AddMembers(typeDeclarationSyntax)) + .NormalizeWhitespace(); + } +} diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Models/HierarchyInfo.cs b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Models/HierarchyInfo.cs new file mode 100644 index 00000000..f04d9014 --- /dev/null +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Models/HierarchyInfo.cs @@ -0,0 +1,58 @@ +// 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. + +// This file is ported and adapted from ComputeSharp (Sergio0694/ComputeSharp), +// more info in ThirdPartyNotices.txt in the root of the project. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Extensions; +using CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Helpers; +using static Microsoft.CodeAnalysis.SymbolDisplayTypeQualificationStyle; + +namespace CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Models; + +/// +/// A model describing the hierarchy info for a specific type. +/// +/// The filename hint for the current type. +/// The metadata name for the current type. +/// The modifiers for the type declaration. +/// Gets the namespace for the current type. +/// Gets the sequence of type definitions containing the current type. +internal sealed partial record HierarchyInfo( + string FilenameHint, + string MetadataName, + EquatableArray Modifiers, + string Namespace, + EquatableArray Hierarchy) +{ + /// + /// Creates a new instance from a given . + /// + /// The input instance to gather info for. + /// A instance describing . + public static HierarchyInfo From(INamedTypeSymbol typeSymbol) + { + using ImmutableArrayBuilder hierarchy = ImmutableArrayBuilder.Rent(); + + for (INamedTypeSymbol? parent = typeSymbol; + parent is not null; + parent = parent.ContainingType) + { + hierarchy.Add(new TypeInfo( + parent.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat), + parent.TypeKind, + parent.IsRecord)); + } + + return new( + typeSymbol.GetFullyQualifiedMetadataName(), + typeSymbol.MetadataName, + ImmutableArray.Create((ushort)SyntaxKind.PartialKeyword), + typeSymbol.ContainingNamespace.ToDisplayString(new(typeQualificationStyle: NameAndContainingTypesAndNamespaces)), + hierarchy.ToImmutable()); + } +} diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Models/RegisteredServiceInfo.cs b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Models/RegisteredServiceInfo.cs new file mode 100644 index 00000000..c25e2896 --- /dev/null +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Models/RegisteredServiceInfo.cs @@ -0,0 +1,20 @@ +// 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 CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Helpers; + +namespace CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Models; + +/// +/// A model for a singleton service registration. +/// +/// The registration kind for the service. +/// The fully qualified type name of the implementation type. +/// The fully qualified type names of dependent services for . +/// The fully qualified type names for the services to register for . +internal sealed record RegisteredServiceInfo( + ServiceRegistrationKind RegistrationKind, + string ImplementationFullyQualifiedTypeName, + EquatableArray RequiredServiceFullyQualifiedTypeNames, + EquatableArray ServiceFullyQualifiedTypeNames); diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Models/ServiceCollectionInfo.cs b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Models/ServiceCollectionInfo.cs new file mode 100644 index 00000000..27926a92 --- /dev/null +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Models/ServiceCollectionInfo.cs @@ -0,0 +1,14 @@ +// 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 CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Helpers; + +namespace CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Models; + +/// +/// A model for a service collection method. +/// +/// The instance for the method. +/// The sequence of instances for services to register. +internal sealed record ServiceCollectionInfo(ServiceProviderMethodInfo Method, EquatableArray Services); diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Models/ServiceProviderMethodInfo.cs b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Models/ServiceProviderMethodInfo.cs new file mode 100644 index 00000000..0fa5243a --- /dev/null +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Models/ServiceProviderMethodInfo.cs @@ -0,0 +1,22 @@ +// 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 CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Helpers; + +namespace CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Models; + +/// +/// A model for a method producing a service provider. +/// +/// The instance for the containing type for the method. +/// The method name. +/// The name of the service collection parameter. +/// Whether the method returns . +/// The method modifiers. +internal sealed record ServiceProviderMethodInfo( + HierarchyInfo Hierarchy, + string MethodName, + string ServiceCollectionParameterName, + bool ReturnsVoid, + EquatableArray Modifiers); diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Models/ServiceRegistrationKind.cs b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Models/ServiceRegistrationKind.cs new file mode 100644 index 00000000..877366aa --- /dev/null +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Models/ServiceRegistrationKind.cs @@ -0,0 +1,21 @@ +// 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. + +namespace CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Models; + +/// +/// Indicates the kind of service registration being used. +/// +internal enum ServiceRegistrationKind +{ + /// + /// A singleton service. + /// + Singleton, + + /// + /// A transient service. + /// + Transient +} diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Models/TypeInfo.cs b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Models/TypeInfo.cs new file mode 100644 index 00000000..29ec7496 --- /dev/null +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Models/TypeInfo.cs @@ -0,0 +1,46 @@ +// 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 Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; + +namespace CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Models; + +/// +/// A model describing a type info in a type hierarchy. +/// +/// The qualified name for the type. +/// The type of the type in the hierarchy. +/// Whether the type is a record type. +internal sealed record TypeInfo(string QualifiedName, TypeKind Kind, bool IsRecord) +{ + /// + /// Creates a instance for the current info. + /// + /// A instance for the current info. + public TypeDeclarationSyntax GetSyntax() + { + // Create the partial type declaration with the kind. + // This code produces a class declaration as follows: + // + // + // { + // } + // + // Note that specifically for record declarations, we also need to explicitly add the open + // and close brace tokens, otherwise member declarations will not be formatted correctly. + return Kind switch + { + TypeKind.Struct => StructDeclaration(QualifiedName), + TypeKind.Interface => InterfaceDeclaration(QualifiedName), + TypeKind.Class when IsRecord => + RecordDeclaration(Token(SyntaxKind.RecordKeyword), QualifiedName) + .WithOpenBraceToken(Token(SyntaxKind.OpenBraceToken)) + .WithCloseBraceToken(Token(SyntaxKind.CloseBraceToken)), + _ => ClassDeclaration(QualifiedName) + }; + } +} diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/ServiceProviderGenerator.Execute.cs b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/ServiceProviderGenerator.Execute.cs new file mode 100644 index 00000000..ec0b001c --- /dev/null +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/ServiceProviderGenerator.Execute.cs @@ -0,0 +1,343 @@ +// ------------------------------------------------------ +// Copyright (C) Microsoft. All rights reserved. +// ------------------------------------------------------ + +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Extensions; +using CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Helpers; +using CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Models; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; + +namespace CommunityToolkit.Extensions.DependencyInjection.SourceGenerators; + +/// +partial class ServiceProviderGenerator : IIncrementalGenerator +{ + /// + /// Helpers to generate the service registrations. + /// + private static class Execute + { + /// + /// A shared annotation used to track arguments. + /// + public static readonly SyntaxAnnotation ArgumentAnnotation = new(); + + /// + /// Checks whether the input is a valid target for generation. + /// + /// The input instance to analyze. + /// The cancellation token to use. + /// Whether is a valid generation target. + public static bool IsSyntaxTarget(SyntaxNode syntaxNode, CancellationToken token) + { + return syntaxNode.IsKind(SyntaxKind.MethodDeclaration); + } + + /// + /// Gathers the info on all registered singleton services. + /// + /// The current instance with the provided info. + /// The cancellation token to use. + /// The gathered info for the current service collection, if available. + public static ServiceCollectionInfo? GetSingletonInfo(GeneratorAttributeSyntaxContext context, CancellationToken token) + { + return GetInfo(ServiceRegistrationKind.Singleton, context, token); + } + + /// + /// Gathers the info on all registered transient services. + /// + /// The current instance with the provided info. + /// The cancellation token to use. + /// The gathered info for the current service collection, if available. + public static ServiceCollectionInfo? GetTransientInfo(GeneratorAttributeSyntaxContext context, CancellationToken token) + { + return GetInfo(ServiceRegistrationKind.Transient, context, token); + } + + /// + /// Gathers the info on all registered services. + /// + /// The registration kind to use. + /// The current instance with the provided info. + /// The cancellation token to use. + /// The gathered info for the current service collection, if available. + private static ServiceCollectionInfo? GetInfo(ServiceRegistrationKind registrationKind, GeneratorAttributeSyntaxContext context, CancellationToken token) + { + // Ensure that the target syntax node is valid: + // - It has to be a method declaration + // - The method has a single parameter of type Microsoft.Extensions.DependencyInjection.IServiceCollection + // - The method returns void or Microsoft.Extensions.DependencyInjection.IServiceCollection + if (context.TargetNode is not MethodDeclarationSyntax methodDeclaration || + context.TargetSymbol is not IMethodSymbol { Parameters: [{ } parameterSymbol] } methodSymbol || + !parameterSymbol.Type.HasFullyQualifiedMetadataName("Microsoft.Extensions.DependencyInjection.IServiceCollection") || + !(methodSymbol.ReturnsVoid || methodSymbol.ReturnType.HasFullyQualifiedMetadataName("Microsoft.Extensions.DependencyInjection.IServiceCollection"))) + { + return null; + } + + // Gather the basic method info + HierarchyInfo hierarchy = HierarchyInfo.From(methodSymbol.ContainingType); + string methodName = methodSymbol.Name; + + token.ThrowIfCancellationRequested(); + + using ImmutableArrayBuilder methodModifiers = ImmutableArrayBuilder.Rent(); + + // Gather all method modifiers + foreach (SyntaxToken modifier in methodDeclaration.Modifiers) + { + methodModifiers.Add((ushort)modifier.Kind()); + } + + token.ThrowIfCancellationRequested(); + + using ImmutableArrayBuilder serviceInfo = ImmutableArrayBuilder.Rent(); + + // Gather all registered services + foreach (AttributeData attributeData in context.Attributes) + { + token.ThrowIfCancellationRequested(); + + if (attributeData.ConstructorArguments is [ + { Kind: TypedConstantKind.Type, Value: INamedTypeSymbol { InstanceConstructors: [IMethodSymbol implementationConstructor, ..] } implementationType }, + { Kind: TypedConstantKind.Array, Values: ImmutableArray serviceTypes }]) + { + // Gather all dependent services for the implementation type + ImmutableArray constructorArgumentTypes = ImmutableArray.CreateRange( + items: implementationConstructor.Parameters, + selector: static parameter => parameter.Type.GetFullyQualifiedName()); + + string implementationTypeName = implementationType.GetFullyQualifiedName(); + ImmutableArray serviceTypeNames = ImmutableArray.Empty; + + // If there are no specified service types, use the implementation type itself as service type. This is pretty + // common for eg. factory types, which don't need to be mocked and are just registered as concrete types. + if (serviceTypes.IsEmpty) + { + serviceTypeNames = ImmutableArray.Create(implementationTypeName); + } + else + { + using ImmutableArrayBuilder builder = ImmutableArrayBuilder.Rent(); + + // Otherwise, simply gather all service types for the current service registration + foreach (TypedConstant serviceType in serviceTypes) + { + if (serviceType is { Kind: TypedConstantKind.Type, Value: INamedTypeSymbol serviceTypeSymbol }) + { + builder.Add(serviceTypeSymbol.GetFullyQualifiedName()); + } + } + + serviceTypeNames = builder.ToImmutable(); + } + + // Create the model fully describing the current service registration + serviceInfo.Add(new RegisteredServiceInfo( + RegistrationKind: registrationKind, + ImplementationFullyQualifiedTypeName: implementationTypeName, + ServiceFullyQualifiedTypeNames: serviceTypeNames, + RequiredServiceFullyQualifiedTypeNames: constructorArgumentTypes)); + } + } + + ServiceProviderMethodInfo methodInfo = new( + hierarchy, + methodName, + parameterSymbol.Name, + methodSymbol.ReturnsVoid, + methodModifiers.ToImmutable()); + + return new(methodInfo, serviceInfo.ToImmutable()); + } + + /// + /// Gets a instance with the gathered info. + /// + /// The input instance with the services info. + /// A instance with the gathered info. + public static CompilationUnitSyntax GetSyntax(ServiceCollectionInfo info) + { + using ImmutableArrayBuilder registrationStatements = ImmutableArrayBuilder.Rent(); + + foreach (RegisteredServiceInfo serviceInfo in info.Services) + { + // The first service type always acts as "main" registration, and should always be present + if (serviceInfo.ServiceFullyQualifiedTypeNames.AsSpan() is not [string rootServiceTypeName, ..ReadOnlySpan dependentServiceTypeNames]) + { + continue; + } + + using ImmutableArrayBuilder constructorArguments = ImmutableArrayBuilder.Rent(); + + // Prepare the dependent services for the implementation type + foreach (string constructorServiceType in serviceInfo.RequiredServiceFullyQualifiedTypeNames) + { + // Create an argument for each constructor parameter: + // + // global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredServices(); + constructorArguments.Add( + Argument( + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName("global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions"), + GenericName(Identifier("GetRequiredService")) + .AddTypeArgumentListArguments(IdentifierName(constructorServiceType)))) + .AddArgumentListArguments(Argument(IdentifierName(info.Method.ServiceCollectionParameterName)))) + .WithAdditionalAnnotations(ArgumentAnnotation)); + } + + // Prepare the method name, either AddSingleton or AddTransient + string registrationMethod = $"Add{serviceInfo.RegistrationKind}"; + + // Special case when the service is a singleton and no dependent services are present, just use eager instantiation instead: + // + // global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(, typeof(), new ()); + if (serviceInfo.RegistrationKind == ServiceRegistrationKind.Singleton && constructorArguments.Span.IsEmpty) + { + registrationStatements.Add( + ExpressionStatement( + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName("global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions"), + IdentifierName("AddSingleton"))) + .AddArgumentListArguments( + Argument(IdentifierName(info.Method.ServiceCollectionParameterName)), + Argument(TypeOfExpression(IdentifierName(rootServiceTypeName))), + Argument( + ObjectCreationExpression(IdentifierName(serviceInfo.ImplementationFullyQualifiedTypeName)) + .WithArgumentList(ArgumentList()))))); + } + else + { + // Register the main implementation type when at least a dependent service is needed: + // + // global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.(, typeof(), static services => new ()); + registrationStatements.Add( + ExpressionStatement( + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName("global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions"), + IdentifierName(registrationMethod))) + .AddArgumentListArguments( + Argument(IdentifierName(info.Method.ServiceCollectionParameterName)), + Argument(TypeOfExpression(IdentifierName(rootServiceTypeName))), + Argument( + SimpleLambdaExpression(Parameter(Identifier("services"))) + .AddModifiers(Token(SyntaxKind.StaticKeyword)) + .WithExpressionBody( + ObjectCreationExpression(IdentifierName(serviceInfo.ImplementationFullyQualifiedTypeName)) + .AddArgumentListArguments(constructorArguments.ToArray())))))); + } + + // Register all secondary services, if any + foreach (string dependentServiceType in dependentServiceTypeNames) + { + // Register the main implementation type: + // + // global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.(, typeof(), static services => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredServices(services)); + registrationStatements.Add( + ExpressionStatement( + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName("global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions"), + IdentifierName(registrationMethod))) + .AddArgumentListArguments( + Argument(IdentifierName(info.Method.ServiceCollectionParameterName)), + Argument(TypeOfExpression(IdentifierName(dependentServiceType))), + Argument( + SimpleLambdaExpression(Parameter(Identifier("services"))) + .AddModifiers(Token(SyntaxKind.StaticKeyword)) + .WithExpressionBody( + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName("global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions"), + GenericName(Identifier("GetRequiredService")) + .AddTypeArgumentListArguments(IdentifierName(rootServiceTypeName)))) + .AddArgumentListArguments(Argument(IdentifierName("services")))))))); + } + } + + // Return the input service provider, if needed: + // + // return ; + if (!info.Method.ReturnsVoid) + { + registrationStatements.Add(ReturnStatement(IdentifierName(info.Method.ServiceCollectionParameterName)).WithLeadingTrivia(Comment(" "))); + } + + // Prepare the return type: either void or IServiceCollection + TypeSyntax returnType = info.Method.ReturnsVoid switch + { + true => PredefinedType(Token(SyntaxKind.VoidKeyword)), + false => IdentifierName("global::Microsoft.Extensions.DependencyInjection.IServiceCollection") + }; + + // Get the service collection configuration method declaration: + // + // /// + // [global::System.CodeDom.Compiler.GeneratedCode("...", "...")] + // [global::System.Diagnostics.DebuggerNonUserCode] + // [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + // (global::Microsoft.Extensions.DependencyInjection.IServiceCollection ) + // { + // + // } + MethodDeclarationSyntax configureServicesMethodDeclaration = + MethodDeclaration(returnType, Identifier(info.Method.MethodName)) + .AddModifiers(info.Method.Modifiers.AsImmutableArray().Select(static m => Token((SyntaxKind)m)).ToArray()) + .AddParameterListParameters( + Parameter(Identifier(info.Method.ServiceCollectionParameterName)) + .WithType(IdentifierName("global::Microsoft.Extensions.DependencyInjection.IServiceCollection"))) + .AddBodyStatements(registrationStatements.ToArray()) + .AddAttributeLists( + AttributeList(SingletonSeparatedList( + Attribute(IdentifierName($"global::System.CodeDom.Compiler.GeneratedCode")).AddArgumentListArguments( + AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ServiceProviderGenerator).FullName))), + AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ServiceProviderGenerator).Assembly.GetName().Version.ToString())))))), + AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.DebuggerNonUserCode")))), + AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage"))))) + .WithLeadingTrivia(Comment("/// ")); + + // Create the compilation unit with the generated members: + CompilationUnitSyntax compilationUnit = info.Method.Hierarchy.GetCompilationUnit(ImmutableArray.Create(configureServicesMethodDeclaration)); + + // Format the annotations + FormatArgumentNodes(ref compilationUnit, info.Method.Hierarchy.Hierarchy.AsSpan().Length + 3); + + return compilationUnit; + } + + /// + /// Formats the arguments with a given annotation adding leading whitespace. + /// + /// The target instance to modify. + /// The indentation level to format arguments for. + private static void FormatArgumentNodes(ref CompilationUnitSyntax compilationUnit, int indentationLevel) + { + string whitespace = new(' ', indentationLevel * 4); + + while (compilationUnit.GetAnnotatedNodes(ArgumentAnnotation).FirstOrDefault() is SyntaxNode annotatedNode) + { + // For each argument node, remove the annotation and add a CRLF and the leading whitespace. We can't + // loop over the annotated nodes on the target unit directly, as it's changed for every iteration. + compilationUnit = compilationUnit.ReplaceNode( + annotatedNode, + annotatedNode.WithoutAnnotations(ArgumentAnnotation).WithLeadingTrivia(CarriageReturnLineFeed, Whitespace(whitespace))); + } + } + } +} \ No newline at end of file diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/ServiceProviderGenerator.cs b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/ServiceProviderGenerator.cs new file mode 100644 index 00000000..c49d5ede --- /dev/null +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/ServiceProviderGenerator.cs @@ -0,0 +1,57 @@ +// ------------------------------------------------------ +// Copyright (C) Microsoft. All rights reserved. +// ------------------------------------------------------ + +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Extensions; +using CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Models; + +namespace CommunityToolkit.Extensions.DependencyInjection.SourceGenerators; + +/// +/// A source generator for instances. +/// +[Generator(LanguageNames.CSharp)] +public sealed partial class ServiceProviderGenerator : IIncrementalGenerator +{ + /// + public void Initialize(IncrementalGeneratorInitializationContext context) + { + // Gather info on all singleton service providers to generate + IncrementalValuesProvider singletonServices = + context.SyntaxProvider.ForAttributeWithMetadataName( + fullyQualifiedMetadataName: "CommunityToolkit.Extensions.DependencyInjection.SingletonAttribute", + predicate: Execute.IsSyntaxTarget, + transform: Execute.GetSingletonInfo) + .Where(static info => info is not null)!; + + // Do the same for all transient services + IncrementalValuesProvider transientServices = + context.SyntaxProvider.ForAttributeWithMetadataName( + fullyQualifiedMetadataName: "CommunityToolkit.Extensions.DependencyInjection.TransientAttribute", + predicate: Execute.IsSyntaxTarget, + transform: Execute.GetTransientInfo) + .Where(static info => info is not null)!; + + // Merge the two registration types + IncrementalValuesProvider serviceCollections = singletonServices.Concat(transientServices); + + // Aggregate all discovered services (both singleton and transient) and group them by target method + IncrementalValuesProvider groupedServiceCollections = + serviceCollections.GroupBy( + keySelector: static item => item.Method, + elementsSelector: static item => item.Services, + resultSelector: static (key, elements) => new ServiceCollectionInfo(key, elements)); + + // Generate all service provider methods + context.RegisterSourceOutput(groupedServiceCollections, static (context, info) => + { + CompilationUnitSyntax compilationUnit = Execute.GetSyntax(info); + + context.AddSource($"{info.Method.Hierarchy.FilenameHint}.{info.Method.MethodName}.g.cs", compilationUnit.GetText(Encoding.UTF8)); + }); + } +} From ced0aa7f28353f13e617873d62877f82e29aceb0 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 13 Jul 2023 13:24:47 +0200 Subject: [PATCH 06/22] Remove ObjectPool, port changes from ComputeSharp --- .../Helpers/ImmutableArrayBuilder{T}.cs | 178 +++++++++++------- .../Helpers/ObjectPool{T}.cs | 163 ---------------- 2 files changed, 111 insertions(+), 230 deletions(-) delete mode 100644 components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/ObjectPool{T}.cs diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/ImmutableArrayBuilder{T}.cs b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/ImmutableArrayBuilder{T}.cs index d1cb7161..c8746c1f 100644 --- a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/ImmutableArrayBuilder{T}.cs +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/ImmutableArrayBuilder{T}.cs @@ -6,6 +6,9 @@ // more info in ThirdPartyNotices.txt in the root of the project. using System; +using System.Buffers; +using System.Collections; +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; @@ -16,13 +19,8 @@ namespace CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Helpe /// A helper type to build sequences of values with pooled buffers. /// /// The type of items to create sequences for. -internal struct ImmutableArrayBuilder : IDisposable +internal ref struct ImmutableArrayBuilder { - /// - /// The shared instance to share objects. - /// - private static readonly ObjectPool SharedObjectPool = new(static () => new Writer()); - /// /// The rented instance to use. /// @@ -34,7 +32,7 @@ internal struct ImmutableArrayBuilder : IDisposable /// A instance to write data to. public static ImmutableArrayBuilder Rent() { - return new(SharedObjectPool.Allocate()); + return new(new Writer()); } /// @@ -46,14 +44,21 @@ internal struct ImmutableArrayBuilder : IDisposable this.writer = writer; } - /// - /// Gets the data written to the underlying buffer so far, as a . - /// - [UnscopedRef] - public readonly Span Span + /// + public readonly int Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.writer!.Span; + get => this.writer!.Count; + } + + /// + /// Gets the data written to the underlying buffer so far, as a . + /// + [UnscopedRef] + public readonly ReadOnlySpan WrittenSpan + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.writer!.WrittenSpan; } /// @@ -66,7 +71,7 @@ internal struct ImmutableArrayBuilder : IDisposable /// Adds the specified items to the end of the array. /// /// The items to add at the end of the array. - public readonly void AddRange(ReadOnlySpan items) + public readonly void AddRange(scoped ReadOnlySpan items) { this.writer!.AddRange(items); } @@ -74,7 +79,7 @@ internal struct ImmutableArrayBuilder : IDisposable /// public readonly ImmutableArray ToImmutable() { - T[] array = this.writer!.Span.ToArray(); + T[] array = this.writer!.WrittenSpan.ToArray(); return Unsafe.As>(ref array); } @@ -82,39 +87,46 @@ internal struct ImmutableArrayBuilder : IDisposable /// public readonly T[] ToArray() { - return this.writer!.Span.ToArray(); + return this.writer!.WrittenSpan.ToArray(); + } + + /// + /// Gets an instance for the current builder. + /// + /// An instance for the current builder. + /// + /// The builder should not be mutated while an enumerator is in use. + /// + public readonly IEnumerable AsEnumerable() + { + return this.writer!; } /// public override readonly string ToString() { - return this.writer!.Span.ToString(); + return this.writer!.WrittenSpan.ToString(); } - /// + /// public void Dispose() { Writer? writer = this.writer; this.writer = null; - if (writer is not null) - { - writer.Clear(); - - SharedObjectPool.Free(writer); - } + writer?.Dispose(); } /// /// A class handling the actual buffer writing. /// - private sealed class Writer + private sealed class Writer : ICollection, IDisposable { /// /// The underlying array. /// - private T[] array; + private T?[]? array; /// /// The starting offset within . @@ -126,31 +138,33 @@ internal struct ImmutableArrayBuilder : IDisposable /// public Writer() { - if (typeof(T) == typeof(char)) - { - this.array = new T[1024]; - } - else - { - this.array = new T[8]; - } - + this.array = ArrayPool.Shared.Rent(typeof(T) == typeof(char) ? 1024 : 8); this.index = 0; } - /// - public Span Span + /// + public int Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new(this.array, 0, this.index); + get => this.index; } + /// + public ReadOnlySpan WrittenSpan + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(this.array!, 0, this.index); + } + + /// + bool ICollection.IsReadOnly => true; + /// public void Add(T value) { EnsureCapacity(1); - this.array[this.index++] = value; + this.array![this.index++] = value; } /// @@ -158,22 +172,64 @@ internal struct ImmutableArrayBuilder : IDisposable { EnsureCapacity(items.Length); - items.CopyTo(this.array.AsSpan(this.index)); + items.CopyTo(this.array.AsSpan(this.index)!); this.index += items.Length; } - /// - /// Clears the items in the current writer. - /// - public void Clear() + /// + public void Dispose() { - if (typeof(T) != typeof(char)) - { - this.array.AsSpan(0, this.index).Clear(); - } + T?[]? array = this.array; - this.index = 0; + this.array = null; + + if (array is not null) + { + ArrayPool.Shared.Return(array, clearArray: typeof(T) != typeof(char)); + } + } + + /// + void ICollection.Clear() + { + throw new NotSupportedException(); + } + + /// + bool ICollection.Contains(T item) + { + throw new NotSupportedException(); + } + + /// + void ICollection.CopyTo(T[] array, int arrayIndex) + { + Array.Copy(this.array!, 0, array, arrayIndex, this.index); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + T?[] array = this.array!; + int length = this.index; + + for (int i = 0; i < length; i++) + { + yield return array[i]!; + } + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)this).GetEnumerator(); + } + + /// + bool ICollection.Remove(T item) + { + throw new NotSupportedException(); } /// @@ -183,7 +239,7 @@ internal struct ImmutableArrayBuilder : IDisposable [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EnsureCapacity(int requestedSize) { - if (requestedSize > this.array.Length - this.index) + if (requestedSize > this.array!.Length - this.index) { ResizeBuffer(requestedSize); } @@ -197,27 +253,15 @@ internal struct ImmutableArrayBuilder : IDisposable private void ResizeBuffer(int sizeHint) { int minimumSize = this.index + sizeHint; - int requestedSize = Math.Max(this.array.Length * 2, minimumSize); - T[] newArray = new T[requestedSize]; + T?[] oldArray = this.array!; + T?[] newArray = ArrayPool.Shared.Rent(minimumSize); - Array.Copy(this.array, newArray, this.index); + Array.Copy(oldArray, newArray, this.index); this.array = newArray; + + ArrayPool.Shared.Return(oldArray, clearArray: typeof(T) != typeof(char)); } } } - -/// -/// Private helpers for the type. -/// -internal static class ImmutableArrayBuilder -{ - /// - /// Throws an for "index". - /// - public static void ThrowArgumentOutOfRangeExceptionForIndex() - { - throw new ArgumentOutOfRangeException("index"); - } -} \ No newline at end of file diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/ObjectPool{T}.cs b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/ObjectPool{T}.cs deleted file mode 100644 index eca26ba1..00000000 --- a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/ObjectPool{T}.cs +++ /dev/null @@ -1,163 +0,0 @@ -// 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. - -// Ported from Roslyn, see: https://github.com/dotnet/roslyn/blob/main/src/Dependencies/PooledObjects/ObjectPool%601.cs. - -using System; -using System.Runtime.CompilerServices; -using System.Threading; - -namespace CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Helpers; - -/// -/// -/// Generic implementation of object pooling pattern with predefined pool size limit. The main purpose -/// is that limited number of frequently used objects can be kept in the pool for further recycling. -/// -/// -/// Notes: -/// -/// -/// It is not the goal to keep all returned objects. Pool is not meant for storage. If there -/// is no space in the pool, extra returned objects will be dropped. -/// -/// -/// It is implied that if object was obtained from a pool, the caller will return it back in -/// a relatively short time. Keeping checked out objects for long durations is ok, but -/// reduces usefulness of pooling. Just new up your own. -/// -/// -/// -/// -/// Not returning objects to the pool in not detrimental to the pool's work, but is a bad practice. -/// Rationale: if there is no intent for reusing the object, do not use pool - just use "new". -/// -/// -/// The type of objects to pool. -internal sealed class ObjectPool - where T : class -{ - /// - /// The factory is stored for the lifetime of the pool. We will call this only when pool needs to - /// expand. compared to "new T()", Func gives more flexibility to implementers and faster than "new T()". - /// - private readonly Func factory; - - /// - /// The array of cached items. - /// - private readonly Element[] items; - - /// - /// Storage for the pool objects. The first item is stored in a dedicated field - /// because we expect to be able to satisfy most requests from it. - /// - private T? firstItem; - - /// - /// Creates a new instance with the specified parameters. - /// - /// The input factory to produce items. - public ObjectPool(Func factory) - : this(factory, Environment.ProcessorCount * 2) - { - } - - /// - /// Creates a new instance with the specified parameters. - /// - /// The input factory to produce items. - /// The pool size to use. - public ObjectPool(Func factory, int size) - { - this.factory = factory; - this.items = new Element[size - 1]; - } - - /// - /// Produces a instance. - /// - /// The returned item to use. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public T Allocate() - { - T? item = this.firstItem; - - if (item is null || item != Interlocked.CompareExchange(ref this.firstItem, null, item)) - { - item = AllocateSlow(); - } - - return item; - } - - /// - /// Returns a given instance to the pool. - /// - /// The instance to return. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Free(T obj) - { - if (this.firstItem is null) - { - this.firstItem = obj; - } - else - { - FreeSlow(obj); - } - } - - /// - /// Allocates a new item. - /// - /// The returned item to use. - [MethodImpl(MethodImplOptions.NoInlining)] - private T AllocateSlow() - { - foreach (ref Element element in this.items.AsSpan()) - { - T? instance = element.Value; - - if (instance is not null) - { - if (instance == Interlocked.CompareExchange(ref element.Value, null, instance)) - { - return instance; - } - } - } - - return this.factory(); - } - - /// - /// Frees a given item. - /// - /// The item to return to the pool. - [MethodImpl(MethodImplOptions.NoInlining)] - private void FreeSlow(T obj) - { - foreach (ref Element element in this.items.AsSpan()) - { - if (element.Value is null) - { - element.Value = obj; - - break; - } - } - } - - /// - /// A container for a produced item (using a wrapper to avoid covariance checks). - /// - private struct Element - { - /// - /// The value held at the current element. - /// - internal T? Value; - } -} \ No newline at end of file From 50cb2bdc03a6a323084c089f9263e4b8a8e1cef6 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 13 Jul 2023 23:11:31 +0200 Subject: [PATCH 07/22] Resolve build errors --- .../Diagnostics/DiagnosticDescriptors.cs | 8 ++++---- .../Extensions/ITypeSymbolExtensions.cs | 4 ++-- .../Helpers/ImmutableArrayBuilder{T}.cs | 2 +- .../ServiceProviderGenerator.Execute.cs | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs index 6702e611..a099a052 100644 --- a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs @@ -20,7 +20,7 @@ internal static class DiagnosticDescriptors /// /// public static readonly DiagnosticDescriptor InvalidRegistrationImplementationType = new DiagnosticDescriptor( - id: "SRVCNFG0001", + id: "TKEXDI0001", title: "Invalid registration implementation type", messageFormat: "Cannot register a service of implementation type {0}, as the type has to be a non static, non abstract class with a public constructor", category: typeof(InvalidServiceRegistrationAnalyzer).FullName, @@ -35,7 +35,7 @@ internal static class DiagnosticDescriptors /// /// public static readonly DiagnosticDescriptor InvalidRegistrationServiceType = new DiagnosticDescriptor( - id: "SRVCNFG0002", + id: "TKEXDI0002", title: "Invalid registration service type", messageFormat: "Cannot register a service of implementation type {0} with the type {1}, as there is no implicit type conversion between the two", category: typeof(InvalidServiceRegistrationAnalyzer).FullName, @@ -50,7 +50,7 @@ internal static class DiagnosticDescriptors /// /// public static readonly DiagnosticDescriptor DuplicateImplementationTypeRegistration = new DiagnosticDescriptor( - id: "SRVCNFG0003", + id: "TKEXDI0003", title: "Duplicate implementation type registration", messageFormat: "The implementation type {0} has already been registered on the target service collection", category: typeof(InvalidServiceRegistrationAnalyzer).FullName, @@ -65,7 +65,7 @@ internal static class DiagnosticDescriptors /// /// public static readonly DiagnosticDescriptor DuplicateServiceTypeRegistration = new DiagnosticDescriptor( - id: "SRVCNFG0004", + id: "TKEXDI0004", title: "Duplicate service type registration", messageFormat: "The service type {0} has already been registered on the target service collection", category: typeof(InvalidServiceRegistrationAnalyzer).FullName, diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Extensions/ITypeSymbolExtensions.cs b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Extensions/ITypeSymbolExtensions.cs index f061ade5..fe405922 100644 --- a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Extensions/ITypeSymbolExtensions.cs +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Extensions/ITypeSymbolExtensions.cs @@ -25,7 +25,7 @@ internal static class ITypeSymbolExtensions symbol.AppendFullyQualifiedMetadataName(in builder); - return builder.Span.SequenceEqual(name.AsSpan()); + return builder.WrittenSpan.SequenceEqual(name.AsSpan()); } /// @@ -90,4 +90,4 @@ internal static class ITypeSymbolExtensions BuildFrom(symbol, in builder); } -} \ No newline at end of file +} diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/ImmutableArrayBuilder{T}.cs b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/ImmutableArrayBuilder{T}.cs index c8746c1f..149031e9 100644 --- a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/ImmutableArrayBuilder{T}.cs +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/ImmutableArrayBuilder{T}.cs @@ -19,7 +19,7 @@ namespace CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Helpe /// A helper type to build sequences of values with pooled buffers. /// /// The type of items to create sequences for. -internal ref struct ImmutableArrayBuilder +internal struct ImmutableArrayBuilder : IDisposable { /// /// The rented instance to use. diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/ServiceProviderGenerator.Execute.cs b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/ServiceProviderGenerator.Execute.cs index ec0b001c..6e0f2d63 100644 --- a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/ServiceProviderGenerator.Execute.cs +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/ServiceProviderGenerator.Execute.cs @@ -1,4 +1,4 @@ -// ------------------------------------------------------ +// ------------------------------------------------------ // Copyright (C) Microsoft. All rights reserved. // ------------------------------------------------------ @@ -202,7 +202,7 @@ partial class ServiceProviderGenerator : IIncrementalGenerator // Special case when the service is a singleton and no dependent services are present, just use eager instantiation instead: // // global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(, typeof(), new ()); - if (serviceInfo.RegistrationKind == ServiceRegistrationKind.Singleton && constructorArguments.Span.IsEmpty) + if (serviceInfo.RegistrationKind == ServiceRegistrationKind.Singleton && constructorArguments.Count == 0) { registrationStatements.Add( ExpressionStatement( @@ -340,4 +340,4 @@ partial class ServiceProviderGenerator : IIncrementalGenerator } } } -} \ No newline at end of file +} From 6dd2b67cdbf5267b712b99bf6cff35e3df6940ab Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 14 Jul 2023 11:53:06 +0200 Subject: [PATCH 08/22] Fix file headers --- .../Analyzers/InvalidServiceRegistrationAnalyzer.cs | 2 +- .../Diagnostics/DiagnosticDescriptors.cs | 2 +- .../ServiceProviderGenerator.Execute.cs | 6 +++--- .../ServiceProviderGenerator.cs | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Diagnostics/Analyzers/InvalidServiceRegistrationAnalyzer.cs b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Diagnostics/Analyzers/InvalidServiceRegistrationAnalyzer.cs index 2e428822..0b520a1e 100644 --- a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Diagnostics/Analyzers/InvalidServiceRegistrationAnalyzer.cs +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Diagnostics/Analyzers/InvalidServiceRegistrationAnalyzer.cs @@ -1,4 +1,4 @@ -// Licensnsed to the .NET Foundation under one or more agreements. +// 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. diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs index a099a052..2cc47e39 100644 --- a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs @@ -1,4 +1,4 @@ -// Licensnsed to the .NET Foundation under one or more agreements. +// 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. diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/ServiceProviderGenerator.Execute.cs b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/ServiceProviderGenerator.Execute.cs index 6e0f2d63..efce7ff2 100644 --- a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/ServiceProviderGenerator.Execute.cs +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/ServiceProviderGenerator.Execute.cs @@ -1,6 +1,6 @@ -// ------------------------------------------------------ -// Copyright (C) Microsoft. All rights reserved. -// ------------------------------------------------------ +// 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; using System.Collections.Immutable; diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/ServiceProviderGenerator.cs b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/ServiceProviderGenerator.cs index c49d5ede..364b56d3 100644 --- a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/ServiceProviderGenerator.cs +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/ServiceProviderGenerator.cs @@ -1,6 +1,6 @@ -// ------------------------------------------------------ -// Copyright (C) Microsoft. All rights reserved. -// ------------------------------------------------------ +// 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.Linq; using System.Text; From 5d51e9959fead517a4ad8bf7990d228636feb520 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 14 Jul 2023 12:22:35 +0200 Subject: [PATCH 09/22] Suppress warnings due to missing samples --- .../samples/Extensions.DependencyInjection.Samples.csproj | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/components/Extensions.DependencyInjection/samples/Extensions.DependencyInjection.Samples.csproj b/components/Extensions.DependencyInjection/samples/Extensions.DependencyInjection.Samples.csproj index f00fb85b..8a491826 100644 --- a/components/Extensions.DependencyInjection/samples/Extensions.DependencyInjection.Samples.csproj +++ b/components/Extensions.DependencyInjection/samples/Extensions.DependencyInjection.Samples.csproj @@ -1,6 +1,13 @@ Extensions.DependencyInjection + + + $(NoWarn);TKSMPL0014 From 3e821b5e0dda10081055cc970d696d1368117bfe Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 14 Jul 2023 18:38:48 +0200 Subject: [PATCH 10/22] Add MultiTarget to sample project --- .../samples/MultiTarget.props | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 components/Extensions.DependencyInjection/samples/MultiTarget.props diff --git a/components/Extensions.DependencyInjection/samples/MultiTarget.props b/components/Extensions.DependencyInjection/samples/MultiTarget.props new file mode 100644 index 00000000..0c55bf4f --- /dev/null +++ b/components/Extensions.DependencyInjection/samples/MultiTarget.props @@ -0,0 +1,9 @@ + + + + uwp; + + From 2b5b6d7cc30b5a689a4029c9bc2816cf8b61c1ff Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 14 Jul 2023 18:44:50 +0200 Subject: [PATCH 11/22] Disable WinUI in source project --- .../src/CommunityToolkit.Extensions.DependencyInjection.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj b/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj index 2b400d25..7ad41601 100644 --- a/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj +++ b/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj @@ -6,6 +6,7 @@ 0.0.1 CommunityToolkit.Extensions.DependencyInjection $(PackageIdPrefix).$(ToolkitComponentName) + false From 37c94a4adade0c7259a3aec0ca1fb3112a6e075a Mon Sep 17 00:00:00 2001 From: Arlo Godfrey Date: Fri, 14 Jul 2023 13:38:45 -0500 Subject: [PATCH 12/22] Disabled UWP for dependencyinjection sample --- .../samples/MultiTarget.props | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/Extensions.DependencyInjection/samples/MultiTarget.props b/components/Extensions.DependencyInjection/samples/MultiTarget.props index 0c55bf4f..c55e43fe 100644 --- a/components/Extensions.DependencyInjection/samples/MultiTarget.props +++ b/components/Extensions.DependencyInjection/samples/MultiTarget.props @@ -1,9 +1,9 @@ - - - uwp; - + wasdk; + From 3fc68208b35d89745f163134b1eaa49cb728cbdf Mon Sep 17 00:00:00 2001 From: Arlo Godfrey Date: Fri, 14 Jul 2023 13:39:07 -0500 Subject: [PATCH 13/22] Update tooling with MultiTarget fix --- tooling | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling b/tooling index a852f23d..6664b870 160000 --- a/tooling +++ b/tooling @@ -1 +1 @@ -Subproject commit a852f23dabb110b7a51c068662309d00834d90a1 +Subproject commit 6664b870636edec2635dadbcddd41dcc4609ae59 From 00fa27bad2c337c185ead71360b752d6f3f00edf Mon Sep 17 00:00:00 2001 From: Arlo Godfrey Date: Mon, 17 Jul 2023 09:53:48 -0500 Subject: [PATCH 14/22] Update tooling to allow deleting sample project --- tooling | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling b/tooling index 6664b870..0fdb53e8 160000 --- a/tooling +++ b/tooling @@ -1 +1 @@ -Subproject commit 6664b870636edec2635dadbcddd41dcc4609ae59 +Subproject commit 0fdb53e8deb371d1371812f76d359ce3ac9d51fa From f583055decfcf71f8a06f5cdcdaec6090d857769 Mon Sep 17 00:00:00 2001 From: Arlo Godfrey Date: Mon, 17 Jul 2023 09:54:12 -0500 Subject: [PATCH 15/22] Removed sample project due to build errors --- .../samples/Assets/icon.png | Bin 6192 -> 0 bytes .../samples/Dependencies.props | 31 ------------------ ...ensions.DependencyInjection.Samples.csproj | 15 --------- .../samples/Extensions.DependencyInjection.md | 21 ------------ .../samples/MultiTarget.props | 9 ----- 5 files changed, 76 deletions(-) delete mode 100644 components/Extensions.DependencyInjection/samples/Assets/icon.png delete mode 100644 components/Extensions.DependencyInjection/samples/Dependencies.props delete mode 100644 components/Extensions.DependencyInjection/samples/Extensions.DependencyInjection.Samples.csproj delete mode 100644 components/Extensions.DependencyInjection/samples/Extensions.DependencyInjection.md delete mode 100644 components/Extensions.DependencyInjection/samples/MultiTarget.props diff --git a/components/Extensions.DependencyInjection/samples/Assets/icon.png b/components/Extensions.DependencyInjection/samples/Assets/icon.png deleted file mode 100644 index 5f574ceca233597ecc5d775fd2f516c0d9685cfa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6192 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 O0000 - - - - - - - - - - - - - - - - - - - - - diff --git a/components/Extensions.DependencyInjection/samples/Extensions.DependencyInjection.Samples.csproj b/components/Extensions.DependencyInjection/samples/Extensions.DependencyInjection.Samples.csproj deleted file mode 100644 index 8a491826..00000000 --- a/components/Extensions.DependencyInjection/samples/Extensions.DependencyInjection.Samples.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - Extensions.DependencyInjection - - - $(NoWarn);TKSMPL0014 - - - - - diff --git a/components/Extensions.DependencyInjection/samples/Extensions.DependencyInjection.md b/components/Extensions.DependencyInjection/samples/Extensions.DependencyInjection.md deleted file mode 100644 index 66ec39e4..00000000 --- a/components/Extensions.DependencyInjection/samples/Extensions.DependencyInjection.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: Extensions.DependencyInjection -author: githubaccount -description: TODO: Your experiment's description here -keywords: Extensions.DependencyInjection, Control, Layout -dev_langs: - - csharp -category: Controls -subcategory: Layout -discussion-id: 0 -issue-id: 0 -icon: assets/icon.png ---- - - - - - - - - diff --git a/components/Extensions.DependencyInjection/samples/MultiTarget.props b/components/Extensions.DependencyInjection/samples/MultiTarget.props deleted file mode 100644 index c55e43fe..00000000 --- a/components/Extensions.DependencyInjection/samples/MultiTarget.props +++ /dev/null @@ -1,9 +0,0 @@ - - - - wasdk; - - From e141e19ca442544ed2bd6dc979cfb861a70ca354 Mon Sep 17 00:00:00 2001 From: Arlo Godfrey Date: Mon, 17 Jul 2023 09:58:34 -0500 Subject: [PATCH 16/22] Fixed Wasm/Linux build errors in CI --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7115a47c..ca1cb7bc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,7 +16,7 @@ on: workflow_dispatch: env: - DOTNET_VERSION: ${{ '6.0.x' }} + DOTNET_VERSION: ${{ '7.0.x' }} ENABLE_DIAGNOSTICS: false #COREHOST_TRACE: 1 COREHOST_TRACEFILE: corehosttrace.log From 6f0244ce0bfe8f5d259803fdfafd1fc802a5c1d6 Mon Sep 17 00:00:00 2001 From: Arlo Godfrey Date: Mon, 17 Jul 2023 10:15:55 -0500 Subject: [PATCH 17/22] Update tooling with latest fixes --- tooling | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling b/tooling index 0fdb53e8..7ba6aba5 160000 --- a/tooling +++ b/tooling @@ -1 +1 @@ -Subproject commit 0fdb53e8deb371d1371812f76d359ce3ac9d51fa +Subproject commit 7ba6aba567a7cf2afd27cef5a31c1c6c415fe766 From e6216304280d06d4ef69f35d191c636410e5982b Mon Sep 17 00:00:00 2001 From: Arlo Godfrey Date: Mon, 17 Jul 2023 11:08:58 -0500 Subject: [PATCH 18/22] Update tooling to latest main --- tooling | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling b/tooling index 7ba6aba5..5490aac9 160000 --- a/tooling +++ b/tooling @@ -1 +1 @@ -Subproject commit 7ba6aba567a7cf2afd27cef5a31c1c6c415fe766 +Subproject commit 5490aac98b527e0801cd6403070ce73cf32858eb From 1b6c45f20f57f67d7dbba5ce980bff53135664a8 Mon Sep 17 00:00:00 2001 From: Arlo Godfrey Date: Mon, 17 Jul 2023 11:14:14 -0500 Subject: [PATCH 19/22] Update tooling to latest main --- tooling | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling b/tooling index 5490aac9..57bce71c 160000 --- a/tooling +++ b/tooling @@ -1 +1 @@ -Subproject commit 5490aac98b527e0801cd6403070ce73cf32858eb +Subproject commit 57bce71cea3e692ae1dac0ba811494345e8954d9 From 79fcccd45617f317ce54663e15bcce0526ee8741 Mon Sep 17 00:00:00 2001 From: Arlo Godfrey Date: Mon, 17 Jul 2023 11:18:25 -0500 Subject: [PATCH 20/22] Removed unneeded property --- .../src/CommunityToolkit.Extensions.DependencyInjection.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj b/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj index 7ad41601..2b400d25 100644 --- a/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj +++ b/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj @@ -6,7 +6,6 @@ 0.0.1 CommunityToolkit.Extensions.DependencyInjection $(PackageIdPrefix).$(ToolkitComponentName) - false From 71ada7ccd609e5b0994bfb0631af9376aa1e7616 Mon Sep 17 00:00:00 2001 From: Arlo Date: Mon, 17 Jul 2023 11:26:26 -0500 Subject: [PATCH 21/22] Update version number --- .../src/CommunityToolkit.Extensions.DependencyInjection.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj b/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj index 2b400d25..d9273a5c 100644 --- a/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj +++ b/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj @@ -3,7 +3,7 @@ Extensions.DependencyInjection This package contains Extensions.DependencyInjection. true - 0.0.1 + 8.0.0-beta.1 CommunityToolkit.Extensions.DependencyInjection $(PackageIdPrefix).$(ToolkitComponentName) From 1e29391ad4ea48bc84db8963a6892ac88b9234c3 Mon Sep 17 00:00:00 2001 From: Arlo Godfrey Date: Mon, 17 Jul 2023 12:02:39 -0500 Subject: [PATCH 22/22] Revert "Update version number" This reverts commit 71ada7ccd609e5b0994bfb0631af9376aa1e7616. --- .../src/CommunityToolkit.Extensions.DependencyInjection.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj b/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj index d9273a5c..2b400d25 100644 --- a/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj +++ b/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj @@ -3,7 +3,7 @@ Extensions.DependencyInjection This package contains Extensions.DependencyInjection. true - 8.0.0-beta.1 + 0.0.1 CommunityToolkit.Extensions.DependencyInjection $(PackageIdPrefix).$(ToolkitComponentName)