diff --git a/src/DryIoc/Container.cs b/src/DryIoc/Container.cs index 961980a54..f4b31ed6e 100644 --- a/src/DryIoc/Container.cs +++ b/src/DryIoc/Container.cs @@ -438,7 +438,7 @@ private object ResolveAndCache(int serviceTypeHash, Type serviceType, IfUnresolv if (Interpreter.TryInterpretAndUnwrapContainerException(this, expr, out var instance)) { // Nope. Important to cache expression first before trying to interpret, so that parallel resolutions may already use it. - // todo: @wip ...but what if exception is thrown, isn't it better to avoid caching the bad expression? + // todo: @check ...but what if exception is thrown, isn't it better to avoid caching the bad expression? if (factory.CanCache) TryCacheDefaultFactory(serviceTypeHash, serviceType, expr); return instance; @@ -6654,10 +6654,8 @@ public static FactoryMethod Of(MemberInfo ctorOrMethodOrMember, ServiceInfo fact } if (factoryInfo != null) - { - return new WithFactoryServiceInfo(ctorOrMethodOrMember, factoryInfo); - // Throw.It(Error.PassedMemberIsStaticButInstanceFactoryIsNotNull, ctorOrMethodOrMember, factoryInfo); // todo: @wip - } + Throw.It(Error.PassedMemberIsStaticButInstanceFactoryIsNotNull, ctorOrMethodOrMember, factoryInfo); + return new FactoryMethod(ctorOrMethodOrMember); } @@ -7159,7 +7157,7 @@ public static Made Of(Func getConstructor, public static TypedMade Of( System.Linq.Expressions.Expression> serviceReturningExpr, params Func[] argValues) => - FromExpression(member => _ => DryIoc.FactoryMethod.Of(member), serviceReturningExpr, argValues); + FromExpression(member => _ => DryIoc.FactoryMethod.Of(member), null, serviceReturningExpr, argValues); /// Defines creation info from factory method call Expression without using strings. /// You can supply any/default arguments to factory method, they won't be used, it is only to find the . @@ -7172,12 +7170,8 @@ public static TypedMade Of( Func> getFactoryInfo, System.Linq.Expressions.Expression> serviceReturningExpr, params Func[] argValues) - where TFactory : class - { - getFactoryInfo.ThrowIfNull(); - return FromExpression(member => request => DryIoc.FactoryMethod.Of(member, getFactoryInfo(request)), - serviceReturningExpr, argValues); - } + where TFactory : class => + FromExpression(null, getFactoryInfo.ThrowIfNull(), serviceReturningExpr, argValues); /// Composes Made.Of expression with known factory instance and expression to get a service public static TypedMade Of( @@ -7187,18 +7181,20 @@ public static TypedMade Of( where TFactory : class { factoryInstance.ThrowIfNull(); - return FromExpression( + return FromExpression( member => request => DryIoc.FactoryMethod.Of(member, factoryInstance), - serviceReturningExpr, argValues); + null, serviceReturningExpr, argValues); } - private static TypedMade FromExpression( - Func getFactoryMethodSelector, - System.Linq.Expressions.LambdaExpression serviceReturningExpr, params Func[] argValues) + private static TypedMade FromExpression( + Func eitherGetFactoryMethodSelector, + Func> orGetFactoryInfo, + System.Linq.Expressions.LambdaExpression serviceReturningExpr, + params Func[] argValues) { var callExpr = serviceReturningExpr.ThrowIfNull().Body; if (callExpr.NodeType == ExprType.Convert) // proceed without Cast expression. - return FromExpression(getFactoryMethodSelector, + return FromExpression(eitherGetFactoryMethodSelector, orGetFactoryInfo, System.Linq.Expressions.Expression.Lambda(((System.Linq.Expressions.UnaryExpression)callExpr).Operand, Empty()), argValues); @@ -7245,16 +7241,25 @@ private static TypedMade FromExpression( else return Throw.For>(Error.NotSupportedMadeOfExpression, callExpr); var hasCustomValue = false; + var hasUsedFactoryInfoForParameter = false; var parameterSelector = parameters.IsNullOrEmpty() ? null : - ComposeParameterSelectorFromArgs(ref hasCustomValue, serviceReturningExpr, parameters, argExprs, argValues); + ComposeParameterSelectorFromArgs(ref hasCustomValue, ref hasUsedFactoryInfoForParameter, + orGetFactoryInfo, serviceReturningExpr, parameters, argExprs, argValues); var propertiesAndFieldsSelector = memberBindingExprs == null || memberBindingExprs.Count == 0 ? null : ComposePropertiesAndFieldsSelector(ref hasCustomValue, serviceReturningExpr, memberBindingExprs, argValues); + var factoryMethodSelector = + eitherGetFactoryMethodSelector != null + ? eitherGetFactoryMethodSelector(ctorOrMethodOrMember) : + hasUsedFactoryInfoForParameter + ? (FactoryMethodSelector)(_ => DryIoc.FactoryMethod.Of(ctorOrMethodOrMember)) + : (FactoryMethodSelector)(r => DryIoc.FactoryMethod.Of(ctorOrMethodOrMember, orGetFactoryInfo(r))); + if (!hasCustomValue && parameterSelector == null && propertiesAndFieldsSelector == null) - return new TypedMade(getFactoryMethodSelector(ctorOrMethodOrMember)); - return new WithDetails(getFactoryMethodSelector(ctorOrMethodOrMember), + return new TypedMade(factoryMethodSelector); + return new WithDetails(factoryMethodSelector, parameterSelector, propertiesAndFieldsSelector, hasCustomValue); } @@ -7300,7 +7305,9 @@ internal Made Clone() : new WithDetails(FactoryMethodOrSelector, t, Parameters, PropertiesAndFields, _details); } - private static ParameterSelector ComposeParameterSelectorFromArgs(ref bool hasCustomValue, + private static ParameterSelector ComposeParameterSelectorFromArgs( + ref bool hasCustomValue, ref bool hasUsedFactoryInfoForParameter, + Func> nullOrGetFactoryInfo, System.Linq.Expressions.Expression wholeServiceExpr, ParameterInfo[] paramInfos, IList argExprs, params Func[] argValues) @@ -7311,9 +7318,23 @@ private static ParameterSelector ComposeParameterSelectorFromArgs(ref bool hasCu var paramInfo = paramInfos[i]; var argExpr = argExprs[i]; - // skip the parameter expression passed from the lambda argument, e.g. for the static and extension methods - if (argExpr is System.Linq.Expressions.ParameterExpression) + // If the parameter expression passed from the lambda argument, e.g. for the static and extension methods, + // Then we will be using the argument factory info as a parameter info, + // so for the extension method `f => f.Create()` the factory info for the `f` will be used for the parameter `f` in `Exts.Create(f)`. + if (argExpr is System.Linq.Expressions.ParameterExpression paramExpr) + { + if (nullOrGetFactoryInfo != null && + typeof(TFactory).IsAssignableTo(paramExpr.Type)) + { + hasUsedFactoryInfoForParameter = true; + paramSelector = paramSelector.OverrideWith(req => + p => p.Equals(paramInfo) + ? nullOrGetFactoryInfo(req)?.Details?.To(ParameterServiceInfo.Of(p).WithDetails) + : null); + } + else Throw.It(Error.MadeOfCallExpressionParameterDoesNotCorrespondToTheFactoryInfo, paramExpr, typeof(TFactory)); continue; + } if (argExpr is System.Linq.Expressions.MethodCallExpression methodCallExpr) { @@ -12862,7 +12883,7 @@ internal sealed class InjectedIntoFactoryDummy : Factory } /// Should return value stored in scope - public delegate object CreateScopedValue(); // todo: @wip remove this thing + public delegate object CreateScopedValue(); internal sealed class ScopedItemException { @@ -14482,6 +14503,8 @@ public static readonly int "The member info {0} passed to `Made.Of` or `FactoryMethod.Of` is NOT static, but instance factory is not provided or null"), PassedMemberIsStaticButInstanceFactoryIsNotNull = Of( "You are passing constructor or STATIC member info {0} to `Made.Of` or `FactoryMethod.Of`, but then why are you passing factory INSTANCE: {1}"), + MadeOfCallExpressionParameterDoesNotCorrespondToTheFactoryInfo = Of( + "Made.Of factory method uses parameter expression `{0}` which is not corresponding to the factory info for that parameter: {1}"), UndefinedMethodWhenGettingTheSingleMethod = Of( "Undefined Method '{0}' in Type {1} (including non-public={2})"), UndefinedMethodWhenGettingMethodWithSpecifiedParameters = Of( diff --git a/test/DryIoc.IssuesTests/GHIssue576_Extension_methods_not_being_handled_correctly_in_MadeOf_service_returning_expression.cs b/test/DryIoc.IssuesTests/GHIssue576_Extension_methods_not_being_handled_correctly_in_MadeOf_service_returning_expression.cs index 2f7e36eca..d84d4a439 100644 --- a/test/DryIoc.IssuesTests/GHIssue576_Extension_methods_not_being_handled_correctly_in_MadeOf_service_returning_expression.cs +++ b/test/DryIoc.IssuesTests/GHIssue576_Extension_methods_not_being_handled_correctly_in_MadeOf_service_returning_expression.cs @@ -9,7 +9,8 @@ public class GHIssue576_Extension_methods_not_being_handled_correctly_in_MadeOf_ public int Run() { Test_factory_extension_method(); - return 1; + Test_factory_extension_method_for_the_factory_with_the_service_key(); + return 2; } [Test] @@ -33,6 +34,27 @@ public void Test_factory_extension_method() Assert.AreEqual(typeof(Foo).FullName, ((SerilogLogger)foo.Logger).TypeName); } + [Test] + public void Test_factory_extension_method_for_the_factory_with_the_service_key() + { + var container = new Container(); + + var loggerFactory = new SerilogLoggerFactory(); + + container.RegisterInstance(loggerFactory, serviceKey: "obscured"); + + container.Register( + Made.Of(_ => ServiceInfo.Of(serviceKey: "obscured"), + f => f.CreateLogger(Arg.Index(0)), + r => r.Parent.ImplementationType), + setup: Setup.With(condition: r => r.Parent.ImplementationType != null)); + + container.Register(); + var foo = container.Resolve(); + + Assert.AreEqual(typeof(Foo).FullName, ((SerilogLogger)foo.Logger).TypeName); + } + public sealed class Foo { public readonly ILogger Logger; diff --git a/test/DryIoc.TestRunner/Program.cs b/test/DryIoc.TestRunner/Program.cs index bb7e0a0d0..85dd60cd0 100644 --- a/test/DryIoc.TestRunner/Program.cs +++ b/test/DryIoc.TestRunner/Program.cs @@ -11,7 +11,6 @@ public static void Main() RunAllTests(); // new GHIssue576_Extension_methods_not_being_handled_correctly_in_MadeOf_service_returning_expression().Run(); - // new GHIssue116_ReOpened_DryIoc_Resolve_with_decorators_goes_wrong_for_parallel_execution().Run(); // new RequiredPropertiesTests().Run(); // new PropertyResolutionTests().Run(); @@ -64,6 +63,7 @@ void Run(Func run, string name = null) new RequiredPropertiesTests(), new SelectConstructorWithAllResolvableArgumentTests(), new Issue107_NamedScopesDependingOnResolvedTypes(), + new Issue548_After_registering_a_factory_Func_is_returned_instead_of_the_result_of_Func(), new GHIssue116_ReOpened_DryIoc_Resolve_with_decorators_goes_wrong_for_parallel_execution(), new GHIssue378_InconsistentResolutionFailure(), new GHIssue380_ExportFactory_throws_Container_disposed_exception(), @@ -72,20 +72,20 @@ void Run(Func run, string name = null) new GHIssue402_Inconsistent_transient_disposable_behavior_when_using_Made(), new GHIssue406_Allow_the_registration_of_the_partially_closed_implementation_type(), new GHIssue460_Getting_instance_from_parent_scope_even_if_replaced_by_Use(), - new Issue548_After_registering_a_factory_Func_is_returned_instead_of_the_result_of_Func(), new GHIssue470_Regression_v5_when_resolving_Func_of_IEnumerable_of_IService_with_Parameter(), + new GHIssue471_Regression_v5_using_Rules_SelectKeyedOverDefaultFactory(), new GHIssue506_WithConcreteTypeDynamicRegistrations_hides_failed_dependency_resolution(), new GHIssue507_Transient_resolve_with_opening_scope_using_factory_func_in_singleton(), new GHIssue508_Throws_when_lazy_resolve_after_explicit_create_using_factory_func_from_within_scope(), - new GHIssue471_Regression_v5_using_Rules_SelectKeyedOverDefaultFactory(), new GHIssue532_WithUseInterpretation_still_use_DynamicMethod_and_ILEmit(), new GHIssue536_DryIoc_Exception_in_a_Constructor_of_a_Dependency_does_tunnel_through_Resolve_call(), new GHIssue546_Generic_type_constraint_resolution_doesnt_see_arrays_as_IEnumerable(), new GHIssue554_System_NullReferenceException_Object_reference_not_set_to_an_instance_of_an_object(), new GHIssue555_ConcreteTypeDynamicRegistrations_is_not_working_with_MicrosoftDependencyInjectionRules(), - new GHIssue565_Is_ignoring_ReuseScoped_setting_expected_behaviour_when_also_set_to_openResolutionScope(), new GHIssue557_WithFactorySelector_allows_to_Resolve_the_keyed_service_as_non_keyed(), new GHIssue559_Possible_inconsistent_behaviour(), + new GHIssue565_Is_ignoring_ReuseScoped_setting_expected_behaviour_when_also_set_to_openResolutionScope(), + new GHIssue576_Extension_methods_not_being_handled_correctly_in_MadeOf_service_returning_expression(), }; // Parallel.ForEach(tests, x => Run(x.Run)); // todo: @perf enable and test when more tests are added diff --git a/test/DryIoc.UnitTests/InjectionRulesTests.cs b/test/DryIoc.UnitTests/InjectionRulesTests.cs index 9cf7a5b9f..7f1513d49 100644 --- a/test/DryIoc.UnitTests/InjectionRulesTests.cs +++ b/test/DryIoc.UnitTests/InjectionRulesTests.cs @@ -304,7 +304,7 @@ public void MadeOf_should_inform_on_absence_of_member() Assert.AreSame(Error.NameOf(Error.PassedCtorOrMemberIsNull), Error.NameOf(ex.Error)); } - // [Test] // todo: @wip @fixme + [Test] public void MadeOf_should_inform_on_presence_of_factory_info_for_static_member() { var c = new Container();