Types;
Base type B has method M.
Derived type D1 derives from B, and implements interface I1 with methods from B.
Derived type D2 derives from B, and implements interface I2 with methods from B.
Assembly A1 has B.
Assembly A2 has D1.
Assembly A3 has I1 and D2.
I2 can be in any assembly.
In the trimmer:
D1 is marked instantiated.
-> B is marked instantiated (as a base type)
-> D1's interfaces are marked, and the implementation methods are marked
-> B.M() is marked
-> I.M() is added as a base method for B.M()
Later, B.M() is passed to IsMethodNeededByInstantiatedTypeDueToPreservedScope()
-> Base methods of B.M() are iterated over
-> I.M() is one of the base methods, and is passed to IsMethodNeedeByTypeDueToPreservedScope()
-> Base methods of I.M() are required, triggering the entire assembly A3 to be mapped.
-> D2 is mapped
-> I2.M() is added as a base method of B.M() -- While base methods of B.M() is being iterated over
There is a psuedo-circular assembly reference with the way we build out TypeMapInfo
A1 (psuedo-)refers to A3 (when A2 is referenced) because B.M() impls I1.M()
A3 refers to A1 because D2 derives from B.
In main the same exact situation doesn't cause issues, and https://github.com/dotnet/linker/pull/3094 fixes this repro for .Net 7, but I don't think a similar situation is guaranteed to never happen. Since base methods can provide interface members, the list of base methods of a type is dependent on which assemblies have been processed by TypeMapInfo at the time GetBaseMethods is called.
Adds a minimal repro for #3112.
The scenario looks like this:
Types; Base type B has method M. Derived type D1 derives from B, and implements interface I1 with methods from B. Derived type D2 derives from B, and implements interface I2 with methods from B.
Assembly A1 has B. Assembly A2 has D1. Assembly A3 has I1 and D2. I2 can be in any assembly.
In the trimmer: D1 is marked instantiated. -> B is marked instantiated (as a base type) -> D1's interfaces are marked, and the implementation methods are marked -> B.M() is marked -> I.M() is added as a base method for B.M()
Later, B.M() is passed to IsMethodNeededByInstantiatedTypeDueToPreservedScope() -> Base methods of B.M() are iterated over -> I.M() is one of the base methods, and is passed to IsMethodNeedeByTypeDueToPreservedScope() -> Base methods of I.M() are required, triggering the entire assembly A3 to be mapped. -> D2 is mapped -> I2.M() is added as a base method of B.M() -- While base methods of B.M() is being iterated over
There is a psuedo-circular assembly reference with the way we build out TypeMapInfo A1 (psuedo-)refers to A3 (when A2 is referenced) because B.M() impls I1.M() A3 refers to A1 because D2 derives from B.
In main the same exact situation doesn't cause issues, and https://github.com/dotnet/linker/pull/3094 fixes this repro for .Net 7, but I don't think a similar situation is guaranteed to never happen. Since base methods can provide interface members, the list of base methods of a type is dependent on which assemblies have been processed by TypeMapInfo at the time GetBaseMethods is called.