Skip to content

Commit

Permalink
fixed: #576 (propagating the factory info into the parameter info for…
Browse files Browse the repository at this point in the history
… the extension method factory
  • Loading branch information
dadhi committed May 25, 2023
1 parent ce37d5f commit a5757c3
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 31 deletions.
73 changes: 48 additions & 25 deletions src/DryIoc/Container.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -7159,7 +7157,7 @@ public static Made Of(Func<Type, ConstructorInfo> getConstructor,
public static TypedMade<TService> Of<TService>(
System.Linq.Expressions.Expression<Func<TService>> serviceReturningExpr,
params Func<Request, object>[] argValues) =>
FromExpression<TService>(member => _ => DryIoc.FactoryMethod.Of(member), serviceReturningExpr, argValues);
FromExpression<object, TService>(member => _ => DryIoc.FactoryMethod.Of(member), null, serviceReturningExpr, argValues);

/// <summary>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 <see cref="MethodInfo"/>.</summary>
Expand All @@ -7172,12 +7170,8 @@ public static TypedMade<TService> Of<TFactory, TService>(
Func<Request, ServiceInfo.Typed<TFactory>> getFactoryInfo,
System.Linq.Expressions.Expression<Func<TFactory, TService>> serviceReturningExpr,
params Func<Request, object>[] argValues)
where TFactory : class
{
getFactoryInfo.ThrowIfNull();
return FromExpression<TService>(member => request => DryIoc.FactoryMethod.Of(member, getFactoryInfo(request)),
serviceReturningExpr, argValues);
}
where TFactory : class =>
FromExpression<TFactory, TService>(null, getFactoryInfo.ThrowIfNull(), serviceReturningExpr, argValues);

/// <summary>Composes Made.Of expression with known factory instance and expression to get a service</summary>
public static TypedMade<TService> Of<TFactory, TService>(
Expand All @@ -7187,18 +7181,20 @@ public static TypedMade<TService> Of<TFactory, TService>(
where TFactory : class
{
factoryInstance.ThrowIfNull();
return FromExpression<TService>(
return FromExpression<TFactory, TService>(
member => request => DryIoc.FactoryMethod.Of(member, factoryInstance),
serviceReturningExpr, argValues);
null, serviceReturningExpr, argValues);
}

private static TypedMade<TService> FromExpression<TService>(
Func<MemberInfo, FactoryMethodSelector> getFactoryMethodSelector,
System.Linq.Expressions.LambdaExpression serviceReturningExpr, params Func<Request, object>[] argValues)
private static TypedMade<TService> FromExpression<TFactory, TService>(
Func<MemberInfo, FactoryMethodSelector> eitherGetFactoryMethodSelector,
Func<Request, ServiceInfo.Typed<TFactory>> orGetFactoryInfo,
System.Linq.Expressions.LambdaExpression serviceReturningExpr,
params Func<Request, object>[] argValues)
{
var callExpr = serviceReturningExpr.ThrowIfNull().Body;
if (callExpr.NodeType == ExprType.Convert) // proceed without Cast expression.
return FromExpression<TService>(getFactoryMethodSelector,
return FromExpression<TFactory, TService>(eitherGetFactoryMethodSelector, orGetFactoryInfo,
System.Linq.Expressions.Expression.Lambda(((System.Linq.Expressions.UnaryExpression)callExpr).Operand,
Empty<System.Linq.Expressions.ParameterExpression>()),
argValues);
Expand Down Expand Up @@ -7245,16 +7241,25 @@ private static TypedMade<TService> FromExpression<TService>(
else return Throw.For<TypedMade<TService>>(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<TService>(getFactoryMethodSelector(ctorOrMethodOrMember));
return new WithDetails<TService>(getFactoryMethodSelector(ctorOrMethodOrMember),
return new TypedMade<TService>(factoryMethodSelector);
return new WithDetails<TService>(factoryMethodSelector,
parameterSelector, propertiesAndFieldsSelector, hasCustomValue);
}

Expand Down Expand Up @@ -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<TFactory>(
ref bool hasCustomValue, ref bool hasUsedFactoryInfoForParameter,
Func<Request, ServiceInfo.Typed<TFactory>> nullOrGetFactoryInfo,
System.Linq.Expressions.Expression wholeServiceExpr, ParameterInfo[] paramInfos,
IList<System.Linq.Expressions.Expression> argExprs,
params Func<Request, object>[] argValues)
Expand All @@ -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)
{
Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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<ILoggerFactory>(loggerFactory, serviceKey: "obscured");

container.Register(
Made.Of(_ => ServiceInfo.Of<ILoggerFactory>(serviceKey: "obscured"),
f => f.CreateLogger(Arg.Index<Type>(0)),
r => r.Parent.ImplementationType),
setup: Setup.With(condition: r => r.Parent.ImplementationType != null));

container.Register<Foo>();
var foo = container.Resolve<Foo>();

Assert.AreEqual(typeof(Foo).FullName, ((SerilogLogger)foo.Logger).TypeName);
}

public sealed class Foo
{
public readonly ILogger Logger;
Expand Down
8 changes: 4 additions & 4 deletions test/DryIoc.TestRunner/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -64,6 +63,7 @@ void Run(Func<int> 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(),
Expand All @@ -72,20 +72,20 @@ void Run(Func<int> 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
Expand Down
2 changes: 1 addition & 1 deletion test/DryIoc.UnitTests/InjectionRulesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down

0 comments on commit a5757c3

Please sign in to comment.