From 82dedcebd503da65c4b8f1fca12eb0a8533a2021 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Fri, 18 Mar 2022 09:46:29 -0700 Subject: [PATCH 1/6] Run tests using VS Code. --- .vscode/tasks.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index d52dc9b5b..e2ca9ac64 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -3,6 +3,7 @@ { "args": [ "build", + "${workspaceFolder}/Autofac.sln", "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], @@ -17,6 +18,24 @@ }, "problemMatcher": "$msCompile", "type": "shell" + }, + { + "args": [ + "test", + "${workspaceFolder}/Autofac.sln", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary", + "--filter", + "FullyQualifiedName!~Benchmark" + ], + "command": "dotnet", + "group": { + "isDefault": true, + "kind": "test" + }, + "label": "test", + "problemMatcher": "$msCompile", + "type": "process" } ], "version": "2.0.0" From 219ba418d53b5dba622e59c09f4c7949f4e17828 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Fri, 18 Mar 2022 09:56:31 -0700 Subject: [PATCH 2/6] Fix #1315: Use base type for open generic arg matching. --- .../OpenGenerics/OpenGenericServiceBinder.cs | 29 ++++++++++++++++++- .../Registration/OpenGenericTests.cs | 23 +++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs b/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs index 951de798f..c03dcd5c6 100644 --- a/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs +++ b/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs @@ -173,7 +173,18 @@ public static bool TryBindOpenGenericDelegateService( if (!serviceType.IsInterface) { - return TryFindServiceArgumentsForImplementation(implementationType, serviceGenericArguments, serviceTypeDefinition.GetGenericArguments()); + var baseType = GetGenericBaseType(implementationType, serviceTypeDefinition); + if (baseType == null) + { + // If it's not an interface, the implementation type MUST have derived from + // the generic service type at some point or there's no way to cast. + return Array.Empty(); + } + + return TryFindServiceArgumentsForImplementation( + implementationType, + serviceGenericArguments, + baseType.GenericTypeArguments); } var availableArguments = GetInterfaces(implementationType, serviceType) @@ -199,6 +210,22 @@ public static bool TryBindOpenGenericDelegateService( .ToArray(); } + private static Type? GetGenericBaseType(Type implementationType, Type serviceTypeDefinition) + { + var baseType = implementationType.BaseType; + while (baseType != null) + { + if (baseType.IsGenericType && baseType.GetGenericTypeDefinition() == serviceTypeDefinition) + { + break; + } + + baseType = baseType.BaseType; + } + + return baseType; + } + private static Type[] GetInterfaces(Type implementationType, Type serviceType) => implementationType.GetInterfaces() .Where(i => i.Name == serviceType.Name && i.Namespace == serviceType.Namespace) diff --git a/test/Autofac.Specification.Test/Registration/OpenGenericTests.cs b/test/Autofac.Specification.Test/Registration/OpenGenericTests.cs index 6717c6c97..2c74fe511 100644 --- a/test/Autofac.Specification.Test/Registration/OpenGenericTests.cs +++ b/test/Autofac.Specification.Test/Registration/OpenGenericTests.cs @@ -42,7 +42,30 @@ public void ResolveWithMultipleCandidatesLimitedByGenericConstraintsShouldSuccee Assert.NotNull(resolved); } + // Issue #1315: Class services fail to resolve if the names on the type + // arguments are changed. + [Fact] + public void ResolveClassWithRenamedTypeArguments() + { + var containerBuilder = new ContainerBuilder(); + containerBuilder.RegisterGeneric(typeof(DerivedRepository<,>)).As(typeof(BaseRepository<,>)); + + var container = containerBuilder.Build(); + var resolved = container.Resolve>(); + Assert.IsType>(resolved); + } + private class SelfComponent : IImplementedInterface { } + + private class BaseRepository + { + } + + // Issue #1315: Class services fail to resolve if the names on the type + // arguments are changed. + private class DerivedRepository : BaseRepository + { + } } From 043253f5a9750e48032ac226ba4e39d4bb928464 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Fri, 18 Mar 2022 09:58:48 -0700 Subject: [PATCH 3/6] Fix typo in test namespace - "Constraints" --- .../Autofac.Specification.Test/Registration/OpenGenericTests.cs | 2 +- .../Graph1/{GenericContraints => GenericConstraints}/A.cs | 2 +- .../ClassWithParameterlessButNotPublicConstructor.cs | 2 +- .../Graph1/{GenericContraints => GenericConstraints}/IA.cs | 2 +- .../Graph1/{GenericContraints => GenericConstraints}/IB.cs | 2 +- .../{GenericContraints => GenericConstraints}/Required.cs | 2 +- .../{GenericContraints => GenericConstraints}/Unrelated.cs | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) rename test/Autofac.Specification.Test/Resolution/Graph1/{GenericContraints => GenericConstraints}/A.cs (80%) rename test/Autofac.Specification.Test/Resolution/Graph1/{GenericContraints => GenericConstraints}/ClassWithParameterlessButNotPublicConstructor.cs (82%) rename test/Autofac.Specification.Test/Resolution/Graph1/{GenericContraints => GenericConstraints}/IA.cs (74%) rename test/Autofac.Specification.Test/Resolution/Graph1/{GenericContraints => GenericConstraints}/IB.cs (75%) rename test/Autofac.Specification.Test/Resolution/Graph1/{GenericContraints => GenericConstraints}/Required.cs (79%) rename test/Autofac.Specification.Test/Resolution/Graph1/{GenericContraints => GenericConstraints}/Unrelated.cs (78%) diff --git a/test/Autofac.Specification.Test/Registration/OpenGenericTests.cs b/test/Autofac.Specification.Test/Registration/OpenGenericTests.cs index 2c74fe511..21ecfe215 100644 --- a/test/Autofac.Specification.Test/Registration/OpenGenericTests.cs +++ b/test/Autofac.Specification.Test/Registration/OpenGenericTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Autofac Project. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. -using Autofac.Test.Scenarios.Graph1.GenericContraints; +using Autofac.Test.Scenarios.Graph1.GenericConstraints; namespace Autofac.Specification.Test.Registration; diff --git a/test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/A.cs b/test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/A.cs similarity index 80% rename from test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/A.cs rename to test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/A.cs index abd1111d1..6d88e8590 100644 --- a/test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/A.cs +++ b/test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/A.cs @@ -1,7 +1,7 @@ // Copyright (c) Autofac Project. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. -namespace Autofac.Test.Scenarios.Graph1.GenericContraints; +namespace Autofac.Test.Scenarios.Graph1.GenericConstraints; public class A : IA { diff --git a/test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/ClassWithParameterlessButNotPublicConstructor.cs b/test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/ClassWithParameterlessButNotPublicConstructor.cs similarity index 82% rename from test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/ClassWithParameterlessButNotPublicConstructor.cs rename to test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/ClassWithParameterlessButNotPublicConstructor.cs index 1e031cfba..8195b6672 100644 --- a/test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/ClassWithParameterlessButNotPublicConstructor.cs +++ b/test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/ClassWithParameterlessButNotPublicConstructor.cs @@ -1,7 +1,7 @@ // Copyright (c) Autofac Project. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. -namespace Autofac.Test.Scenarios.Graph1.GenericContraints; +namespace Autofac.Test.Scenarios.Graph1.GenericConstraints; public class ClassWithParameterlessButNotPublicConstructor { diff --git a/test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/IA.cs b/test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/IA.cs similarity index 74% rename from test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/IA.cs rename to test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/IA.cs index 9077aba56..71189c6ae 100644 --- a/test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/IA.cs +++ b/test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/IA.cs @@ -1,7 +1,7 @@ // Copyright (c) Autofac Project. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. -namespace Autofac.Test.Scenarios.Graph1.GenericContraints; +namespace Autofac.Test.Scenarios.Graph1.GenericConstraints; public interface IA { diff --git a/test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/IB.cs b/test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/IB.cs similarity index 75% rename from test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/IB.cs rename to test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/IB.cs index ad7fd282b..cdaec1d56 100644 --- a/test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/IB.cs +++ b/test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/IB.cs @@ -1,7 +1,7 @@ // Copyright (c) Autofac Project. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. -namespace Autofac.Test.Scenarios.Graph1.GenericContraints; +namespace Autofac.Test.Scenarios.Graph1.GenericConstraints; public interface IB { diff --git a/test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/Required.cs b/test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/Required.cs similarity index 79% rename from test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/Required.cs rename to test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/Required.cs index d083a5fe3..cd426f378 100644 --- a/test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/Required.cs +++ b/test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/Required.cs @@ -1,7 +1,7 @@ // Copyright (c) Autofac Project. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. -namespace Autofac.Test.Scenarios.Graph1.GenericContraints; +namespace Autofac.Test.Scenarios.Graph1.GenericConstraints; public class Required : IB { diff --git a/test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/Unrelated.cs b/test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/Unrelated.cs similarity index 78% rename from test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/Unrelated.cs rename to test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/Unrelated.cs index ca934c3a1..3d6d0907a 100644 --- a/test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/Unrelated.cs +++ b/test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/Unrelated.cs @@ -1,7 +1,7 @@ // Copyright (c) Autofac Project. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. -namespace Autofac.Test.Scenarios.Graph1.GenericContraints; +namespace Autofac.Test.Scenarios.Graph1.GenericConstraints; public class Unrelated : IB where T : class, new() From 68f6d8fc8d61f47a981532b7ba559affcfccdd0b Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Fri, 18 Mar 2022 10:09:36 -0700 Subject: [PATCH 4/6] Test for #1315: Ensure it works multiple inheritance levels deep. --- .../Registration/OpenGenericTests.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/Autofac.Specification.Test/Registration/OpenGenericTests.cs b/test/Autofac.Specification.Test/Registration/OpenGenericTests.cs index 21ecfe215..a35541c78 100644 --- a/test/Autofac.Specification.Test/Registration/OpenGenericTests.cs +++ b/test/Autofac.Specification.Test/Registration/OpenGenericTests.cs @@ -55,6 +55,19 @@ public void ResolveClassWithRenamedTypeArguments() Assert.IsType>(resolved); } + // Issue #1315: Class services fail to resolve if the names on the type + // arguments are changed. We should be able to handle a deep inheritance chain. + [Fact] + public void ResolveClassTwoLevelsDownWithRenamedTypeArguments() + { + var containerBuilder = new ContainerBuilder(); + containerBuilder.RegisterGeneric(typeof(TwoLevelsDerivedRepository<,>)).As(typeof(BaseRepository<,>)); + + var container = containerBuilder.Build(); + var resolved = container.Resolve>(); + Assert.IsType>(resolved); + } + private class SelfComponent : IImplementedInterface { } @@ -68,4 +81,8 @@ private class BaseRepository private class DerivedRepository : BaseRepository { } + + private class TwoLevelsDerivedRepository : DerivedRepository + { + } } From 1f2f747938703f9f92819927d2bf07c60c8429e8 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Mon, 28 Mar 2022 15:21:48 -0700 Subject: [PATCH 5/6] Explanatory comment for why we walk back the hierarchy. --- .../OpenGenerics/OpenGenericServiceBinder.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs b/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs index c03dcd5c6..eac9852c7 100644 --- a/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs +++ b/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs @@ -173,6 +173,28 @@ public static bool TryBindOpenGenericDelegateService( if (!serviceType.IsInterface) { + /* Issue #1315: We walk backwards in the inheritance hierarchy to + * find the open generic because that's the only way to ensure the + * generic argument names match. + * + * If you have a class BaseClass and a derived class + * DerivedClass : BaseClass then then arguments need + * to line up name-wise like A2 -> T1 A1 -> T2 + * + * If you do a typeof(BaseClass<,>) then the generic arguments are + * named T1 and T2, which doesn't help us line up the arguments in + * the derived class because the names and order have changed. + * However, if you start at the derived class and walk backwards up + * the inheritance hierarchy then instead of getting the original + * T1/T2 naming, we get the names A1/A2 - the symbols as lined up by + * the compiler. Having those symbols aligned means that, later, we + * can do a name-based match to populate the generic arguments. + * + * This is the same reason why Type.GetInterfaces() "just works" - + * it's the generic in relation to the derived/implementing type + * rather than just typeof(MyInterface<,>), so the names all line + * up. + */ var baseType = GetGenericBaseType(implementationType, serviceTypeDefinition); if (baseType == null) { From 8c2a9c2875b6ed896c8e15108c5a6404a23d7db2 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Mon, 28 Mar 2022 15:23:55 -0700 Subject: [PATCH 6/6] Minor reword on that clarifying manifesto. --- .../Features/OpenGenerics/OpenGenericServiceBinder.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs b/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs index eac9852c7..e7de4266f 100644 --- a/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs +++ b/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs @@ -179,7 +179,12 @@ public static bool TryBindOpenGenericDelegateService( * * If you have a class BaseClass and a derived class * DerivedClass : BaseClass then then arguments need - * to line up name-wise like A2 -> T1 A1 -> T2 + * to line up name-wise like... + * A2 -> T1 + * A1 -> T2 + * + * Having those symbols aligned means that, later, we can do a + * name-based match to populate the generic arguments. * * If you do a typeof(BaseClass<,>) then the generic arguments are * named T1 and T2, which doesn't help us line up the arguments in @@ -187,8 +192,7 @@ public static bool TryBindOpenGenericDelegateService( * However, if you start at the derived class and walk backwards up * the inheritance hierarchy then instead of getting the original * T1/T2 naming, we get the names A1/A2 - the symbols as lined up by - * the compiler. Having those symbols aligned means that, later, we - * can do a name-based match to populate the generic arguments. + * the compiler. * * This is the same reason why Type.GetInterfaces() "just works" - * it's the generic in relation to the derived/implementing type