From 781ff222e7b771f061fed4c5cb30a8df37a3d089 Mon Sep 17 00:00:00 2001 From: yairh Date: Fri, 22 Oct 2021 11:54:52 +0300 Subject: [PATCH] Allow factories to be registered as specific types. Fixes #151 --- .../RegistrationCalculator.cs | 173 +++-- StrongInject.Tests.Unit/GeneratorTests.cs | 650 ++++++++++++++++++ StrongInject/FactoryAttribute.cs | 26 +- 3 files changed, 802 insertions(+), 47 deletions(-) diff --git a/StrongInject.Generator/RegistrationCalculator.cs b/StrongInject.Generator/RegistrationCalculator.cs index 431c0e1..00758e3 100644 --- a/StrongInject.Generator/RegistrationCalculator.cs +++ b/StrongInject.Generator/RegistrationCalculator.cs @@ -478,14 +478,14 @@ private void AppendFactoryRegistrations(Dictionary } } - private bool CheckValidType(AttributeData registerAttribute, TypedConstant typedConstant, ITypeSymbol module, out INamedTypeSymbol type) + private bool CheckValidType(AttributeData attribute, TypedConstant typedConstant, ITypeSymbol module, out INamedTypeSymbol type) { type = (typedConstant.Value as INamedTypeSymbol)!; if (typedConstant.Value is null) { _reportDiagnostic(InvalidType( (ITypeSymbol)typedConstant.Value!, - registerAttribute.GetLocation(_cancellationToken))); + attribute.GetLocation(_cancellationToken))); return false; } if (type.IsOrReferencesErrorType()) @@ -498,7 +498,7 @@ private bool CheckValidType(AttributeData registerAttribute, TypedConstant typed { _reportDiagnostic(TypeDoesNotHaveAtLeastInternalAccessibility( type, - registerAttribute.GetLocation(_cancellationToken))); + attribute.GetLocation(_cancellationToken))); return false; } @@ -507,7 +507,7 @@ private bool CheckValidType(AttributeData registerAttribute, TypedConstant typed _reportDiagnostic(WarnTypeNotPublic( type, module, - registerAttribute.GetLocation(_cancellationToken))); + attribute.GetLocation(_cancellationToken))); } return true; @@ -577,12 +577,11 @@ private void AppendFactoryMethods( _ => throw new InvalidEnumArgumentException(nameof(registrationsToCalculate), (int)registrationsToCalculate, typeof(RegistrationsToCalculate)) }) { - var instanceSource = CreateInstanceSourceIfFactoryMethod(method, out var attribute); - if (instanceSource is not null) + foreach (var instanceSource in CreateInstanceSourceIfFactoryMethod(method, module)) { - if (instanceSource.IsOpenGeneric) + if (instanceSource is FactoryMethod { IsOpenGeneric: true } factoryMethod) { - genericRegistrations.Add(instanceSource); + genericRegistrations.Add(factoryMethod); } else { @@ -592,62 +591,103 @@ private void AppendFactoryMethods( } } - private FactoryMethod? CreateInstanceSourceIfFactoryMethod(IMethodSymbol method, out AttributeData attribute) + private IEnumerable CreateInstanceSourceIfFactoryMethod(IMethodSymbol method, INamedTypeSymbol module) { - attribute = method.GetAttributes().FirstOrDefault(x + var attribute = method.GetAttributes().FirstOrDefault(x => x.AttributeClass is { } attribute - && attribute.Equals(_wellKnownTypes.FactoryAttribute, SymbolEqualityComparer.Default))!; + && attribute.Equals(_wellKnownTypes.FactoryAttribute, SymbolEqualityComparer.Default))!; + if (attribute is not null) { var countConstructorArguments = attribute.ConstructorArguments.Length; - if (countConstructorArguments != 1) + if (countConstructorArguments is not (1 or 2)) { // Invalid code, ignore - return null; + yield break; } var scope = attribute.ConstructorArguments[0] is { Kind: TypedConstantKind.Enum, Value: int scopeInt } ? (Scope)scopeInt : Scope.InstancePerResolution; - if (method.ReturnType is { SpecialType: not SpecialType.System_Void } returnType) + var asTypes = attribute.ConstructorArguments.Last() + is { Kind: TypedConstantKind.Array, Values: { IsDefaultOrEmpty: false } types } + ? types + : ImmutableArray.Empty; + + if (method.ReturnType.SpecialType == SpecialType.System_Void) { - bool isGeneric = method.TypeParameters.Length > 0; - if (isGeneric && !AllTypeParametersUsedInReturnType(method)) + _reportDiagnostic(FactoryMethodReturnsVoid( + method, + attribute.ApplicationSyntaxReference?.GetSyntax(_cancellationToken).GetLocation() ?? Location.None)); + + yield break; + } + + bool isGeneric = method.TypeParameters.Length > 0; + if (isGeneric && !AllTypeParametersUsedInReturnType(method)) + { + _reportDiagnostic(NotAllTypeParametersUsedInReturnType( + method, + attribute.ApplicationSyntaxReference?.GetSyntax(_cancellationToken).GetLocation() ?? Location.None)); + + yield break; + } + + if (isGeneric && !asTypes.IsEmpty) + { + _reportDiagnostic(GenericFactoryMethodWithAsTypes(method, attribute.GetLocation(_cancellationToken))); + yield break; + } + + foreach (var param in method.Parameters) + { + if (param.RefKind != RefKind.None) { - _reportDiagnostic(NotAllTypeParametersUsedInReturnType( + _reportDiagnostic(FactoryMethodParameterIsPassedByRef( method, - attribute.ApplicationSyntaxReference?.GetSyntax(_cancellationToken).GetLocation() ?? Location.None)); + param, + param.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(_cancellationToken).GetLocation() ?? Location.None)); + yield break; + } + } - return null; + var returnType = method.ReturnType; + var factoryMethod = returnType.IsWellKnownTaskType(_wellKnownTypes, out var taskOfType) + ? new FactoryMethod(method, taskOfType, scope, isGeneric, IsAsync: true) + : new FactoryMethod(method, returnType, scope, isGeneric, IsAsync: false); + + if (asTypes.IsEmpty) + { + yield return factoryMethod; + yield break; + } + + var factoryOfType = factoryMethod.FactoryOfType; + + foreach (var asType in asTypes) + { + if (!CheckValidType(attribute, asType, module, out var target)) + { + // Invalid code, ignore + continue; } - foreach (var param in method.Parameters) + if (target.IsUnboundGenericType) { - if (param.RefKind != RefKind.None) - { - _reportDiagnostic(FactoryMethodParameterIsPassedByRef( - method, - param, - param.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(_cancellationToken).GetLocation() ?? Location.None)); - return null; - } + _reportDiagnostic(FactoryMethodWithUnboundGenericAsTypes(method, target, attribute.GetLocation(_cancellationToken))); + continue; } - if (returnType.IsWellKnownTaskType(_wellKnownTypes, out var taskOfType)) + if (_compilation.ClassifyConversion(factoryOfType, target) is not { IsImplicit: true, IsNumeric: false, IsUserDefined: false }) { - return new FactoryMethod(method, taskOfType, scope, isGeneric, IsAsync: true); + _reportDiagnostic(FactoryMethodDoesNotHaveSuitableConversion(method, factoryOfType, target, attribute.GetLocation(_cancellationToken))); + continue; } - return new FactoryMethod(method, returnType, scope, isGeneric, IsAsync: false); + yield return ForwardedInstanceSource.Create(target, factoryMethod); } - - _reportDiagnostic(FactoryMethodReturnsVoid( - method, - attribute.ApplicationSyntaxReference?.GetSyntax(_cancellationToken).GetLocation() ?? Location.None)); } - - return null; } private static bool AllTypeParametersUsedInReturnType(IMethodSymbol method) @@ -1455,7 +1495,7 @@ private static Diagnostic FactoryMethodReturnsVoid(IMethodSymbol methodSymbol, L return Diagnostic.Create( new DiagnosticDescriptor( "SI0014", - "Factory Method returns void", + "Factory method returns void", "Factory method '{0}' returns void.", "StrongInject", DiagnosticSeverity.Error, @@ -1501,7 +1541,7 @@ private static Diagnostic NotAllTypeParametersUsedInReturnType(IMethodSymbol met return Diagnostic.Create( new DiagnosticDescriptor( "SI0020", - "All type parameters must be used in return type of generic Factory Method", + "All type parameters must be used in return type of generic factory method", "All type parameters must be used in return type of generic factory method '{0}'", "StrongInject", DiagnosticSeverity.Error, @@ -1559,8 +1599,8 @@ private static Diagnostic DecoratorFactoryMethodDoesNotHaveParameterOfDecoratedT return Diagnostic.Create( new DiagnosticDescriptor( "SI0024", - "Decorator Factory Method does not have a parameter of decorated type", - "Decorator Factory '{0}' does not have a parameter of decorated type '{1}'.", + "Decorator factory method does not have a parameter of decorated type", + "Decorator factory '{0}' does not have a parameter of decorated type '{1}'.", "StrongInject", DiagnosticSeverity.Error, isEnabledByDefault: true), @@ -1574,8 +1614,8 @@ private static Diagnostic DecoratorFactoryMethodHasMultipleParametersOfDecorated return Diagnostic.Create( new DiagnosticDescriptor( "SI0025", - "Decorator Factory Method has multiple constructor parameters of decorated type", - "Decorator Factory '{0}' has multiple constructor parameters of decorated type '{1}'.", + "Decorator Factory method has multiple constructor parameters of decorated type", + "Decorator factory '{0}' has multiple constructor parameters of decorated type '{1}'.", "StrongInject", DiagnosticSeverity.Error, isEnabledByDefault: true), @@ -1674,6 +1714,51 @@ private static Diagnostic MismatchingNumberOfTypeParameters(AttributeData regist registeredAsType); } + private static Diagnostic FactoryMethodDoesNotHaveSuitableConversion(IMethodSymbol method, ITypeSymbol returnType, INamedTypeSymbol registeredAsType, Location location) + { + return Diagnostic.Create( + new DiagnosticDescriptor( + "SI0032", + "Return type of factory method does not have an identity, implicit reference, boxing or nullable conversion to registered as type", + "Return type '{0}' of '{1}' does not have an identity, implicit reference, boxing or nullable conversion to '{2}'.", + "StrongInject", + DiagnosticSeverity.Error, + isEnabledByDefault: true), + location, + returnType, + method.Name, + registeredAsType); + } + + private static Diagnostic GenericFactoryMethodWithAsTypes(IMethodSymbol method, Location location) + { + return Diagnostic.Create( + new DiagnosticDescriptor( + "SI0033", + "Factory method cannot be registered as specific types since it is generic.", + "Factory method '{0}' cannot be registered as specific types since it is generic.", + "StrongInject", + DiagnosticSeverity.Error, + isEnabledByDefault: true), + location, + method.Name); + } + + private static Diagnostic FactoryMethodWithUnboundGenericAsTypes(IMethodSymbol method, INamedTypeSymbol asType, Location location) + { + return Diagnostic.Create( + new DiagnosticDescriptor( + "SI0034", + "Factory method cannot be registered as an instance of open generic type.", + "Factory method '{0}' cannot be registered as an instance of open generic type '{1}'.", + "StrongInject", + DiagnosticSeverity.Error, + isEnabledByDefault: true), + location, + method.Name, + asType); + } + private static Diagnostic WarnSimpleRegistrationImplementingFactory(ITypeSymbol type, ITypeSymbol factoryType, Location location) { return Diagnostic.Create( @@ -1694,7 +1779,7 @@ private static Diagnostic WarnFactoryMethodNotPublicStaticOrProtected(ITypeSymbo return Diagnostic.Create( new DiagnosticDescriptor( "SI1002", - "Factory Method is not either public and static, or protected, and containing module is not a container, so will be ignored", + "Factory method is not either public and static, or protected, and containing module is not a container, so will be ignored", "Factory method '{0}' is not either public and static, or protected, and containing module '{1}' is not a container, so will be ignored.", "StrongInject", DiagnosticSeverity.Warning, diff --git a/StrongInject.Tests.Unit/GeneratorTests.cs b/StrongInject.Tests.Unit/GeneratorTests.cs index c75fba1..f023f97 100644 --- a/StrongInject.Tests.Unit/GeneratorTests.cs +++ b/StrongInject.Tests.Unit/GeneratorTests.cs @@ -26464,6 +26464,656 @@ public void Dispose() { throw new global::System.NotImplementedException(); } +}"); + } + + [Fact] + public void TestFactoryMethodAsSingleType() + { + string userSource = @" +using StrongInject; + +public partial class Container : IContainer +{ + [Factory(typeof(object))] A GetA() => null; +} + +public class A{}"; + var comp = RunGeneratorWithStrongInjectReference(userSource, out var generatorDiagnostics, out var generated); + generatorDiagnostics.Verify(); + comp.GetDiagnostics().Verify(); + var file = Assert.Single(generated); + file.Should().BeIgnoringLineEndings(@"#pragma warning disable CS1998 +partial class Container +{ + private int _disposed = 0; + private bool Disposed => _disposed != 0; + public void Dispose() + { + var disposed = global::System.Threading.Interlocked.Exchange(ref this._disposed, 1); + if (disposed != 0) + return; + } + + TResult global::StrongInject.IContainer.Run(global::System.Func func, TParam param) + { + if (Disposed) + throw new global::System.ObjectDisposedException(nameof(Container)); + global::A a_0_1; + global::System.Object object_0_0; + a_0_1 = this.GetA(); + try + { + object_0_0 = (global::System.Object)a_0_1; + } + catch + { + global::StrongInject.Helpers.Dispose(a_0_1); + throw; + } + + TResult result; + try + { + result = func(object_0_0, param); + } + finally + { + global::StrongInject.Helpers.Dispose(a_0_1); + } + + return result; + } + + global::StrongInject.Owned global::StrongInject.IContainer.Resolve() + { + if (Disposed) + throw new global::System.ObjectDisposedException(nameof(Container)); + global::A a_0_1; + global::System.Object object_0_0; + a_0_1 = this.GetA(); + try + { + object_0_0 = (global::System.Object)a_0_1; + } + catch + { + global::StrongInject.Helpers.Dispose(a_0_1); + throw; + } + + return new global::StrongInject.Owned(object_0_0, () => + { + global::StrongInject.Helpers.Dispose(a_0_1); + }); + } +}"); + } + + [Fact] + public void TestFactoryMethodAsMultipleTypes() + { + string userSource = @" +using StrongInject; + +public partial class Container : IContainer, IContainer +{ + [Factory(typeof(IB), typeof(IC))] A GetA() => null; +} + +public class A : IB, IC{} +public interface IB{} +public interface IC{}"; + var comp = RunGeneratorWithStrongInjectReference(userSource, out var generatorDiagnostics, out var generated); + generatorDiagnostics.Verify(); + comp.GetDiagnostics().Verify(); + var file = Assert.Single(generated); + file.Should().BeIgnoringLineEndings(@"#pragma warning disable CS1998 +partial class Container +{ + private int _disposed = 0; + private bool Disposed => _disposed != 0; + public void Dispose() + { + var disposed = global::System.Threading.Interlocked.Exchange(ref this._disposed, 1); + if (disposed != 0) + return; + } + + TResult global::StrongInject.IContainer.Run(global::System.Func func, TParam param) + { + if (Disposed) + throw new global::System.ObjectDisposedException(nameof(Container)); + global::A a_0_1; + global::IB iB_0_0; + a_0_1 = this.GetA(); + try + { + iB_0_0 = (global::IB)a_0_1; + } + catch + { + global::StrongInject.Helpers.Dispose(a_0_1); + throw; + } + + TResult result; + try + { + result = func(iB_0_0, param); + } + finally + { + global::StrongInject.Helpers.Dispose(a_0_1); + } + + return result; + } + + global::StrongInject.Owned global::StrongInject.IContainer.Resolve() + { + if (Disposed) + throw new global::System.ObjectDisposedException(nameof(Container)); + global::A a_0_1; + global::IB iB_0_0; + a_0_1 = this.GetA(); + try + { + iB_0_0 = (global::IB)a_0_1; + } + catch + { + global::StrongInject.Helpers.Dispose(a_0_1); + throw; + } + + return new global::StrongInject.Owned(iB_0_0, () => + { + global::StrongInject.Helpers.Dispose(a_0_1); + }); + } + + TResult global::StrongInject.IContainer.Run(global::System.Func func, TParam param) + { + if (Disposed) + throw new global::System.ObjectDisposedException(nameof(Container)); + global::A a_0_1; + global::IC iC_0_0; + a_0_1 = this.GetA(); + try + { + iC_0_0 = (global::IC)a_0_1; + } + catch + { + global::StrongInject.Helpers.Dispose(a_0_1); + throw; + } + + TResult result; + try + { + result = func(iC_0_0, param); + } + finally + { + global::StrongInject.Helpers.Dispose(a_0_1); + } + + return result; + } + + global::StrongInject.Owned global::StrongInject.IContainer.Resolve() + { + if (Disposed) + throw new global::System.ObjectDisposedException(nameof(Container)); + global::A a_0_1; + global::IC iC_0_0; + a_0_1 = this.GetA(); + try + { + iC_0_0 = (global::IC)a_0_1; + } + catch + { + global::StrongInject.Helpers.Dispose(a_0_1); + throw; + } + + return new global::StrongInject.Owned(iC_0_0, () => + { + global::StrongInject.Helpers.Dispose(a_0_1); + }); + } +}"); + } + + [Fact] + public void TestFactoryMethodAsMultipleTypesFirstTypeInvalid() + { + string userSource = @" +using StrongInject; + +public partial class Container : IContainer, IContainer +{ + [Factory(typeof(IB), typeof(IC))] A GetA() => null; +} + +public class A : IC{} +public interface IB{} +public interface IC{}"; + var comp = RunGeneratorWithStrongInjectReference(userSource, out var generatorDiagnostics, out var generated); + generatorDiagnostics.Verify( + // (4,22): Error SI0102: Error while resolving dependencies for 'IB': We have no source for instance of type 'IB' + // Container + new DiagnosticResult("SI0102", @"Container", DiagnosticSeverity.Error).WithLocation(4, 22), + // (6,6): Error SI0032: Return type 'A' of 'GetA' does not have an identity, implicit reference, boxing or nullable conversion to 'IB'. + // Factory(typeof(IB), typeof(IC)) + new DiagnosticResult("SI0032", @"Factory(typeof(IB), typeof(IC))", DiagnosticSeverity.Error).WithLocation(6, 6)); + comp.GetDiagnostics().Verify(); + var file = Assert.Single(generated); + file.Should().BeIgnoringLineEndings(@"#pragma warning disable CS1998 +partial class Container +{ + private int _disposed = 0; + private bool Disposed => _disposed != 0; + public void Dispose() + { + var disposed = global::System.Threading.Interlocked.Exchange(ref this._disposed, 1); + if (disposed != 0) + return; + } + + TResult global::StrongInject.IContainer.Run(global::System.Func func, TParam param) + { + throw new global::System.NotImplementedException(); + } + + global::StrongInject.Owned global::StrongInject.IContainer.Resolve() + { + throw new global::System.NotImplementedException(); + } + + TResult global::StrongInject.IContainer.Run(global::System.Func func, TParam param) + { + if (Disposed) + throw new global::System.ObjectDisposedException(nameof(Container)); + global::A a_0_1; + global::IC iC_0_0; + a_0_1 = this.GetA(); + try + { + iC_0_0 = (global::IC)a_0_1; + } + catch + { + global::StrongInject.Helpers.Dispose(a_0_1); + throw; + } + + TResult result; + try + { + result = func(iC_0_0, param); + } + finally + { + global::StrongInject.Helpers.Dispose(a_0_1); + } + + return result; + } + + global::StrongInject.Owned global::StrongInject.IContainer.Resolve() + { + if (Disposed) + throw new global::System.ObjectDisposedException(nameof(Container)); + global::A a_0_1; + global::IC iC_0_0; + a_0_1 = this.GetA(); + try + { + iC_0_0 = (global::IC)a_0_1; + } + catch + { + global::StrongInject.Helpers.Dispose(a_0_1); + throw; + } + + return new global::StrongInject.Owned(iC_0_0, () => + { + global::StrongInject.Helpers.Dispose(a_0_1); + }); + } +}"); + } + + [Fact] + public void TestFactoryMethodWithAsTypesNotAutoRegisteredAsSelf() + { + string userSource = @" +using StrongInject; + +public partial class Container : IContainer +{ + [Factory(typeof(object))] A GetA() => null; +} + +public class A{}"; + var comp = RunGeneratorWithStrongInjectReference(userSource, out var generatorDiagnostics, out var generated); + generatorDiagnostics.Verify( + // (4,22): Error SI0102: Error while resolving dependencies for 'A': We have no source for instance of type 'A' + // Container + new DiagnosticResult("SI0102", @"Container", DiagnosticSeverity.Error).WithLocation(4, 22)); + comp.GetDiagnostics().Verify(); + var file = Assert.Single(generated); + file.Should().BeIgnoringLineEndings(@"#pragma warning disable CS1998 +partial class Container +{ + private int _disposed = 0; + private bool Disposed => _disposed != 0; + public void Dispose() + { + var disposed = global::System.Threading.Interlocked.Exchange(ref this._disposed, 1); + if (disposed != 0) + return; + } + + TResult global::StrongInject.IContainer.Run(global::System.Func func, TParam param) + { + throw new global::System.NotImplementedException(); + } + + global::StrongInject.Owned global::StrongInject.IContainer.Resolve() + { + throw new global::System.NotImplementedException(); + } +}"); + } + + [Fact] + public void TestFactoryMethodWithAsTypesCanBeRegisteredAsSelf() + { + string userSource = @" +using StrongInject; + +public partial class Container : IContainer +{ + [Factory(typeof(object), typeof(A))] A GetA() => null; +} + +public class A{}"; + var comp = RunGeneratorWithStrongInjectReference(userSource, out var generatorDiagnostics, out var generated); + generatorDiagnostics.Verify(); + comp.GetDiagnostics().Verify(); + var file = Assert.Single(generated); + file.Should().BeIgnoringLineEndings(@"#pragma warning disable CS1998 +partial class Container +{ + private int _disposed = 0; + private bool Disposed => _disposed != 0; + public void Dispose() + { + var disposed = global::System.Threading.Interlocked.Exchange(ref this._disposed, 1); + if (disposed != 0) + return; + } + + TResult global::StrongInject.IContainer.Run(global::System.Func func, TParam param) + { + if (Disposed) + throw new global::System.ObjectDisposedException(nameof(Container)); + global::A a_0_0; + a_0_0 = this.GetA(); + TResult result; + try + { + result = func(a_0_0, param); + } + finally + { + global::StrongInject.Helpers.Dispose(a_0_0); + } + + return result; + } + + global::StrongInject.Owned global::StrongInject.IContainer.Resolve() + { + if (Disposed) + throw new global::System.ObjectDisposedException(nameof(Container)); + global::A a_0_0; + a_0_0 = this.GetA(); + return new global::StrongInject.Owned(a_0_0, () => + { + global::StrongInject.Helpers.Dispose(a_0_0); + }); + } +}"); + } + + [Fact] + public void TestGenericFactoryMethodCannotBeRegisteredAsSpecificTypes() + { + string userSource = @" +using StrongInject; + +public partial class Container : IContainer +{ + [Factory(typeof(int))] T GetT() => default; +}"; + var comp = RunGeneratorWithStrongInjectReference(userSource, out var generatorDiagnostics, out var generated); + generatorDiagnostics.Verify( + // (4,22): Error SI0102: Error while resolving dependencies for 'int': We have no source for instance of type 'int' + // Container + new DiagnosticResult("SI0102", @"Container", DiagnosticSeverity.Error).WithLocation(4, 22), + // (6,6): Error SI0033: Factory Method 'GetT' cannot be registered as specific types since it is generic. + // Factory(typeof(int)) + new DiagnosticResult("SI0033", @"Factory(typeof(int))", DiagnosticSeverity.Error).WithLocation(6, 6)); + comp.GetDiagnostics().Verify(); + var file = Assert.Single(generated); + file.Should().BeIgnoringLineEndings(@"#pragma warning disable CS1998 +partial class Container +{ + private int _disposed = 0; + private bool Disposed => _disposed != 0; + public void Dispose() + { + var disposed = global::System.Threading.Interlocked.Exchange(ref this._disposed, 1); + if (disposed != 0) + return; + } + + TResult global::StrongInject.IContainer.Run(global::System.Func func, TParam param) + { + throw new global::System.NotImplementedException(); + } + + global::StrongInject.Owned global::StrongInject.IContainer.Resolve() + { + throw new global::System.NotImplementedException(); + } +}"); + } + + [Fact] + public void TestFactoryMethodCannotBeRegisteredAsUnboundType() + { + string userSource = @" +using StrongInject; +using System.Collections.Generic; + +public partial class Container : IContainer> +{ + [Factory(typeof(IList<>))] List GetListT() => default; +}"; + var comp = RunGeneratorWithStrongInjectReference(userSource, out var generatorDiagnostics, out var generated); + generatorDiagnostics.Verify( + // (5,22): Error SI0102: Error while resolving dependencies for 'System.Collections.Generic.IList': We have no source for instance of type 'System.Collections.Generic.IList' + // Container + new DiagnosticResult("SI0102", @"Container", DiagnosticSeverity.Error).WithLocation(5, 22), + // (7,6): Error SI0033: Factory Method 'GetListT' cannot be registered as specific types since it is generic. + // Factory(typeof(IList<>)) + new DiagnosticResult("SI0033", @"Factory(typeof(IList<>))", DiagnosticSeverity.Error).WithLocation(7, 6)); + comp.GetDiagnostics().Verify(); + var file = Assert.Single(generated); + file.Should().BeIgnoringLineEndings(@"#pragma warning disable CS1998 +partial class Container +{ + private int _disposed = 0; + private bool Disposed => _disposed != 0; + public void Dispose() + { + var disposed = global::System.Threading.Interlocked.Exchange(ref this._disposed, 1); + if (disposed != 0) + return; + } + + TResult global::StrongInject.IContainer>.Run(global::System.Func, TParam, TResult> func, TParam param) + { + throw new global::System.NotImplementedException(); + } + + global::StrongInject.Owned> global::StrongInject.IContainer>.Resolve() + { + throw new global::System.NotImplementedException(); + } +}"); + } + + [Fact] + public void TestFactoryMethodAsMultipleTypesWithScope() + { + string userSource = @" +using StrongInject; + +public partial class Container : IContainer, IContainer +{ + [Factory(Scope.SingleInstance, typeof(IB), typeof(IC))] A GetA() => null; +} + +public class A : IB, IC{} +public interface IB{} +public interface IC{}"; + var comp = RunGeneratorWithStrongInjectReference(userSource, out var generatorDiagnostics, out var generated); + generatorDiagnostics.Verify(); + comp.GetDiagnostics().Verify(); + var file = Assert.Single(generated); + file.Should().BeIgnoringLineEndings(@"#pragma warning disable CS1998 +partial class Container +{ + private int _disposed = 0; + private bool Disposed => _disposed != 0; + public void Dispose() + { + var disposed = global::System.Threading.Interlocked.Exchange(ref this._disposed, 1); + if (disposed != 0) + return; + this._lock0.Wait(); + try + { + this._disposeAction0?.Invoke(); + } + finally + { + this._lock0.Release(); + } + } + + private global::A _aField0; + private global::System.Threading.SemaphoreSlim _lock0 = new global::System.Threading.SemaphoreSlim(1); + private global::System.Action _disposeAction0; + private global::A GetAField0() + { + if (!object.ReferenceEquals(_aField0, null)) + return _aField0; + this._lock0.Wait(); + try + { + if (this.Disposed) + throw new global::System.ObjectDisposedException(nameof(Container)); + global::A a_0_0; + a_0_0 = this.GetA(); + this._aField0 = a_0_0; + this._disposeAction0 = () => + { + global::StrongInject.Helpers.Dispose(a_0_0); + }; + } + finally + { + this._lock0.Release(); + } + + return _aField0; + } + + TResult global::StrongInject.IContainer.Run(global::System.Func func, TParam param) + { + if (Disposed) + throw new global::System.ObjectDisposedException(nameof(Container)); + global::A a_0_1; + global::IB iB_0_0; + a_0_1 = GetAField0(); + iB_0_0 = (global::IB)a_0_1; + TResult result; + try + { + result = func(iB_0_0, param); + } + finally + { + } + + return result; + } + + global::StrongInject.Owned global::StrongInject.IContainer.Resolve() + { + if (Disposed) + throw new global::System.ObjectDisposedException(nameof(Container)); + global::A a_0_1; + global::IB iB_0_0; + a_0_1 = GetAField0(); + iB_0_0 = (global::IB)a_0_1; + return new global::StrongInject.Owned(iB_0_0, () => + { + }); + } + + TResult global::StrongInject.IContainer.Run(global::System.Func func, TParam param) + { + if (Disposed) + throw new global::System.ObjectDisposedException(nameof(Container)); + global::A a_0_1; + global::IC iC_0_0; + a_0_1 = GetAField0(); + iC_0_0 = (global::IC)a_0_1; + TResult result; + try + { + result = func(iC_0_0, param); + } + finally + { + } + + return result; + } + + global::StrongInject.Owned global::StrongInject.IContainer.Resolve() + { + if (Disposed) + throw new global::System.ObjectDisposedException(nameof(Container)); + global::A a_0_1; + global::IC iC_0_0; + a_0_1 = GetAField0(); + iC_0_0 = (global::IC)a_0_1; + return new global::StrongInject.Owned(iC_0_0, () => + { + }); + } }"); } } diff --git a/StrongInject/FactoryAttribute.cs b/StrongInject/FactoryAttribute.cs index 1696481..d712670 100644 --- a/StrongInject/FactoryAttribute.cs +++ b/StrongInject/FactoryAttribute.cs @@ -10,14 +10,34 @@ namespace StrongInject [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] public sealed class FactoryAttribute : Attribute { - /// - /// - /// /// The scope of each instance resolved from the method - i.e. how often the method will be called. public FactoryAttribute(Scope scope = Scope.InstancePerResolution) { Scope = scope; + AsTypes = Array.Empty(); } + + /// The scope of each instance resolved from the method - i.e. how often the method will be called. + /// An optional list of types for this to be used as the factory of. + /// If left empty it will be used as a factory of the return type. + /// If not left empty you will have to explicitly register it as the return type if desired. + /// All types must be supertypes of the return type. + public FactoryAttribute(Scope scope, params Type[] asTypes) + { + Scope = scope; + AsTypes = asTypes; + } + + /// An optional list of types for this to be used as the factory of. + /// If left empty it will be used as a factory of the return type. + /// If not left empty you will have to explicitly register it as the return type if desired. + /// All types must be supertypes of the return type. + public FactoryAttribute(params Type[] asTypes) + { + AsTypes = asTypes; + } + public Scope Scope { get; } + public Type[] AsTypes { get; } } }