Skip to content

Commit 8c33ad3

Browse files
Don't remove unused interfaces in library mode (#2711)
This will make sure that we keep all interfaces and all interface method implementations on such type. * Change the solution to rely on the optimization setting only * wip * Add implicit interface implementation case * Edit tests to remove issue-specific code and names * Mark members of CollectedType as kept * Add private interface test and simplify external interface example * Replace early exit for non-interfaces * License headers and use IsMethodNeededByTypeDueToPreservedScope instead of Interface specific version * Add check for static methods before skipping virtual marking Check for optimization before skipping marking interface methods for PreservedScope Add doc comments * Add more clarifying comments, move static method check, rename method Rename IsVirtualNeededByInstantiatedTypeDueToPreservedScope to IsOverrideNeededByInstantiatedTypeDueToPreservedScope Added more comments describing purpose of methods Moved the static interface method check to the method that is called on all types regardless of instantiation * Renames and comment cleanup + tests This doesn't change product behavior, only renamed methods and improved comments. Added new tests for the RootLibrary: - Added a dependency to an "copy" assembly (mainly because I can define a static interface in it) - Added more combinations to the interfaces/classes in the test - Since this uses static interface methods I had to enable "preview" language features for the test project and for the test infra. * More tests for interface behavior Co-authored-by: vitek-karas <vitek.karas@microsoft.com> Co-authored-by: vitek-karas <10670590+vitek-karas@users.noreply.github.com>
1 parent 805c6a7 commit 8c33ad3

File tree

9 files changed

+531
-18
lines changed

9 files changed

+531
-18
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
</PropertyGroup>
3030

3131
<PropertyGroup>
32-
<LangVersion>latest</LangVersion>
32+
<LangVersion>preview</LangVersion>
3333
<AnalysisLevel>latest</AnalysisLevel>
3434
</PropertyGroup>
3535
</Project>

src/linker/Linker.Steps/MarkStep.cs

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -584,11 +584,15 @@ void ProcessMarkedTypesWithInterfaces ()
584584
foreach ((var type, var scope) in typesWithInterfaces) {
585585
// Exception, types that have not been flagged as instantiated yet. These types may not need their interfaces even if the
586586
// interface type is marked
587-
if (!Annotations.IsInstantiated (type) && !Annotations.IsRelevantToVariantCasting (type))
587+
// UnusedInterfaces optimization is turned off mark all interface implementations
588+
bool unusedInterfacesOptimizationEnabled = Context.IsOptimizationEnabled (CodeOptimizations.UnusedInterfaces, type);
589+
if (!Annotations.IsInstantiated (type) && !Annotations.IsRelevantToVariantCasting (type) &&
590+
unusedInterfacesOptimizationEnabled)
588591
continue;
589592

590-
using (ScopeStack.PushScope (scope))
593+
using (ScopeStack.PushScope (scope)) {
591594
MarkInterfaceImplementations (type);
595+
}
592596
}
593597
}
594598

@@ -1719,6 +1723,9 @@ void MarkField (FieldDefinition field, in DependencyInfo reason)
17191723
}
17201724
}
17211725

1726+
/// <summary>
1727+
/// Returns true if the assembly of the <paramref name="scope"></paramref> is not set to link (i.e. action=copy is set for that assembly)
1728+
/// </summary>
17221729
protected virtual bool IgnoreScope (IMetadataScope scope)
17231730
{
17241731
AssemblyDefinition? assembly = Context.Resolve (scope);
@@ -1915,7 +1922,7 @@ internal void MarkStaticConstructorVisibleToReflection (TypeDefinition type, in
19151922
MarkGenericParameterProvider (type);
19161923

19171924
// There are a number of markings we can defer until later when we know it's possible a reference type could be instantiated
1918-
// For example, if no instance of a type exist, then we don't need to mark the interfaces on that type
1925+
// For example, if no instance of a type exist, then we don't need to mark the interfaces on that type -- Note this is not true for static interfaces
19191926
// However, for some other types there is no benefit to deferring
19201927
if (type.IsInterface) {
19211928
// There's no benefit to deferring processing of an interface type until we know a type implementing that interface is marked
@@ -1939,12 +1946,13 @@ internal void MarkStaticConstructorVisibleToReflection (TypeDefinition type, in
19391946
MarkRequirementsForInstantiatedTypes (type);
19401947
}
19411948

1949+
// Save for later once we know which interfaces are marked and then determine which interface implementations and methods to keep
19421950
if (type.HasInterfaces)
19431951
_typesWithInterfaces.Add ((type, ScopeStack.CurrentScope));
19441952

19451953
if (type.HasMethods) {
1946-
// For virtuals that must be preserved, blame the declaring type.
1947-
MarkMethodsIf (type.Methods, IsVirtualNeededByTypeDueToPreservedScope, new DependencyInfo (DependencyKind.VirtualNeededDueToPreservedScope, type));
1954+
// For methods that must be preserved, blame the declaring type.
1955+
MarkMethodsIf (type.Methods, IsMethodNeededByTypeDueToPreservedScope, new DependencyInfo (DependencyKind.VirtualNeededDueToPreservedScope, type));
19481956
if (ShouldMarkTypeStaticConstructor (type) && reason.Kind != DependencyKind.TriggersCctorForCalledMethod) {
19491957
using (ScopeStack.PopToParent ())
19501958
MarkStaticConstructor (type, new DependencyInfo (DependencyKind.CctorForType, type));
@@ -2278,9 +2286,19 @@ void MarkGenericParameter (GenericParameter parameter)
22782286
}
22792287
}
22802288

2281-
bool IsVirtualNeededByTypeDueToPreservedScope (MethodDefinition method)
2282-
{
2283-
if (!method.IsVirtual)
2289+
/// <summary>
2290+
/// Returns true if any of the base methods of the <paramref name="method"/> passed is in an assembly that is not trimmed (i.e. action != trim).
2291+
/// Meant to be used to determine whether methods should be marked regardless of whether it is instantiated or not.
2292+
/// </summary>
2293+
/// <remarks>
2294+
/// When the unusedinterfaces optimization is on, this is used to mark methods that override an abstract method from a non-link assembly and must be kept.
2295+
/// When the unusedinterfaces optimization is off, this will do the same as when on but will also mark interface methods from interfaces defined in a non-link assembly.
2296+
/// If the containing type is instantiated, the caller should also use <see cref="IsMethodNeededByInstantiatedTypeDueToPreservedScope (MethodDefinition)" />
2297+
/// </remarks>
2298+
bool IsMethodNeededByTypeDueToPreservedScope (MethodDefinition method)
2299+
{
2300+
// Static methods may also have base methods in static interface methods. These methods are not captured by IsVirtual and must be checked separately
2301+
if (!(method.IsVirtual || method.IsStatic))
22842302
return false;
22852303

22862304
var base_list = Annotations.GetBaseMethods (method);
@@ -2289,8 +2307,8 @@ bool IsVirtualNeededByTypeDueToPreservedScope (MethodDefinition method)
22892307

22902308
foreach (MethodDefinition @base in base_list) {
22912309
// Just because the type is marked does not mean we need interface methods.
2292-
// if the type is never instantiated, interfaces will be removed
2293-
if (@base.DeclaringType.IsInterface)
2310+
// if the type is never instantiated, interfaces will be removed - but only if the optimization is enabled
2311+
if (@base.DeclaringType.IsInterface && Context.IsOptimizationEnabled (CodeOptimizations.UnusedInterfaces, method.DeclaringType))
22942312
continue;
22952313

22962314
// If the type is marked, we need to keep overrides of abstract members defined in assemblies
@@ -2302,15 +2320,24 @@ bool IsVirtualNeededByTypeDueToPreservedScope (MethodDefinition method)
23022320
if (IgnoreScope (@base.DeclaringType.Scope))
23032321
return true;
23042322

2305-
if (IsVirtualNeededByTypeDueToPreservedScope (@base))
2323+
if (IsMethodNeededByTypeDueToPreservedScope (@base))
23062324
return true;
23072325
}
23082326

23092327
return false;
23102328
}
23112329

2312-
bool IsVirtualNeededByInstantiatedTypeDueToPreservedScope (MethodDefinition method)
2330+
/// <summary>
2331+
/// Returns true if any of the base methods of <paramref name="method" /> is defined in an assembly that is not trimmed (i.e. action!=trim).
2332+
/// This is meant to be used on methods from a type that is known to be instantiated.
2333+
/// </summary>
2334+
/// <remarks>
2335+
/// This is very similar to <see cref="IsMethodNeededByTypeDueToPreservedScope (MethodDefinition)"/>,
2336+
/// but will mark methods from an interface defined in a non-link assembly regardless of the optimization, and does not handle static interface methods.
2337+
/// </remarks>
2338+
bool IsMethodNeededByInstantiatedTypeDueToPreservedScope (MethodDefinition method)
23132339
{
2340+
// Any static interface methods are captured by <see cref="IsVirtualNeededByTypeDueToPreservedScope">, which should be called on all relevant methods so no need to check again here.
23142341
if (!method.IsVirtual)
23152342
return false;
23162343

@@ -2322,7 +2349,7 @@ bool IsVirtualNeededByInstantiatedTypeDueToPreservedScope (MethodDefinition meth
23222349
if (IgnoreScope (@base.DeclaringType.Scope))
23232350
return true;
23242351

2325-
if (IsVirtualNeededByTypeDueToPreservedScope (@base))
2352+
if (IsMethodNeededByTypeDueToPreservedScope (@base))
23262353
return true;
23272354
}
23282355

@@ -3110,7 +3137,7 @@ protected virtual void MarkRequirementsForInstantiatedTypes (TypeDefinition type
31103137
protected virtual IEnumerable<MethodDefinition> GetRequiredMethodsForInstantiatedType (TypeDefinition type)
31113138
{
31123139
foreach (var method in type.Methods) {
3113-
if (IsVirtualNeededByInstantiatedTypeDueToPreservedScope (method))
3140+
if (IsMethodNeededByInstantiatedTypeDueToPreservedScope (method))
31143141
yield return method;
31153142
}
31163143
}

test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.InterfacesTests.g.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,11 @@ public Task InterfaceOnUninstantiatedTypeRemoved ()
2121
return RunTest (allowMissingWarnings: true);
2222
}
2323

24+
[Fact]
25+
public Task InterfaceVariants ()
26+
{
27+
return RunTest (allowMissingWarnings: true);
28+
}
29+
2430
}
2531
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces.Dependencies
5+
{
6+
public interface ICopyLibraryInterface
7+
{
8+
void CopyLibraryInterfaceMethod ();
9+
void CopyLibraryExplicitImplementationInterfaceMethod ();
10+
}
11+
12+
public interface ICopyLibraryStaticInterface
13+
{
14+
static abstract void CopyLibraryStaticInterfaceMethod ();
15+
static abstract void CopyLibraryExplicitImplementationStaticInterfaceMethod ();
16+
}
17+
}

test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/InterfaceOnUninstantiatedTypeRemoved.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces
55
{
6-
[SetupLinkerArgument ("--disable-opt", "unusedinterfaces")]
76
public class InterfaceOnUninstantiatedTypeRemoved
87
{
98
public static void Main ()

0 commit comments

Comments
 (0)