From 6b0da00fb3991b0eea363813c1fae1ca8b1f5838 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Thu, 8 Jul 2021 09:16:08 -0700 Subject: [PATCH] Preserve custom operators (#2125) * Preserve custom operators This will keep custom operators on marked types whenever System.Linq.Expressions is used, and the operator input types are marked. The behavior is enabled by default, and can be disabled by passing --disable-operator-discovery. Addresses https://github.com/mono/linker/issues/1821 * Fix behavior for operators on nullable types * Cleanup and PR feedback - Avoid processing pending operators Dictionary if Linq.Expressions is unused - Allocate this possibly-unused Dictionary lazily - Use readonly field for always-used HashSet - Rename markOperators -> seenLinqExpressions - Clean up ProcessCustomOperators call to make intent more clear - Add comments - Check MetadataType.Int32 instead of searching BCL for Int32 * Remove unnecessary parens * PR feedback - seenLinqExpressions -> _seenLinqExpressions - use List for pending operators instead of HashSet --- .../DiscoverCustomOperatorsHandler.cs | 215 ++++++++++++++++++ src/linker/Linker/DependencyInfo.cs | 2 + src/linker/Linker/Driver.cs | 9 + src/linker/Linker/LinkContext.cs | 2 + .../CanDisableOperatorDiscovery.cs | 31 +++ .../CanPreserveCustomOperators.cs | 93 ++++++++ .../CanPreserveNullableCustomOperators.cs | 85 +++++++ ...anRemoveMethodsNamedLikeCustomOperators.cs | 31 +++ ...oveOperatorsWhenNotUsingLinqExpressions.cs | 41 ++++ .../CustomOperatorsWithUnusedArgumentTypes.cs | 57 +++++ .../TestCases/TestDatabase.cs | 5 + .../Mono.Linker.Tests/TestCases/TestSuites.cs | 6 + 12 files changed, 577 insertions(+) create mode 100644 src/linker/Linker.Steps/DiscoverCustomOperatorsHandler.cs create mode 100644 test/Mono.Linker.Tests.Cases/LinqExpressions/CanDisableOperatorDiscovery.cs create mode 100644 test/Mono.Linker.Tests.Cases/LinqExpressions/CanPreserveCustomOperators.cs create mode 100644 test/Mono.Linker.Tests.Cases/LinqExpressions/CanPreserveNullableCustomOperators.cs create mode 100644 test/Mono.Linker.Tests.Cases/LinqExpressions/CanRemoveMethodsNamedLikeCustomOperators.cs create mode 100644 test/Mono.Linker.Tests.Cases/LinqExpressions/CanRemoveOperatorsWhenNotUsingLinqExpressions.cs create mode 100644 test/Mono.Linker.Tests.Cases/LinqExpressions/CustomOperatorsWithUnusedArgumentTypes.cs diff --git a/src/linker/Linker.Steps/DiscoverCustomOperatorsHandler.cs b/src/linker/Linker.Steps/DiscoverCustomOperatorsHandler.cs new file mode 100644 index 000000000000..e4b2458bddc4 --- /dev/null +++ b/src/linker/Linker.Steps/DiscoverCustomOperatorsHandler.cs @@ -0,0 +1,215 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Diagnostics; +using Mono.Cecil; + +namespace Mono.Linker.Steps +{ + public class DiscoverOperatorsHandler : IMarkHandler + { + LinkContext _context; + bool _seenLinqExpressions; + readonly HashSet _trackedTypesWithOperators; + Dictionary> _pendingOperatorsForType; + + Dictionary> PendingOperatorsForType { + get { + if (_pendingOperatorsForType == null) + _pendingOperatorsForType = new Dictionary> (); + return _pendingOperatorsForType; + } + } + + public DiscoverOperatorsHandler () + { + _trackedTypesWithOperators = new HashSet (); + } + + public void Initialize (LinkContext context, MarkContext markContext) + { + _context = context; + markContext.RegisterMarkTypeAction (ProcessType); + } + + void ProcessType (TypeDefinition type) + { + CheckForLinqExpressions (type); + + // Check for custom operators and either: + // - mark them, if Linq.Expressions was already marked, or + // - track them to be marked in case Linq.Expressions is marked later + var hasOperators = ProcessCustomOperators (type, mark: _seenLinqExpressions); + if (!_seenLinqExpressions) { + if (hasOperators) + _trackedTypesWithOperators.Add (type); + return; + } + + // Mark pending operators defined on other types that reference this type + // (these are only tracked if we have already seen Linq.Expressions) + if (PendingOperatorsForType.TryGetValue (type, out var pendingOperators)) { + foreach (var customOperator in pendingOperators) + MarkOperator (customOperator); + PendingOperatorsForType.Remove (type); + } + } + + void CheckForLinqExpressions (TypeDefinition type) + { + if (_seenLinqExpressions) + return; + + if (type.Namespace != "System.Linq.Expressions" || type.Name != "Expression") + return; + + _seenLinqExpressions = true; + + foreach (var markedType in _trackedTypesWithOperators) + ProcessCustomOperators (markedType, mark: true); + + _trackedTypesWithOperators.Clear (); + } + + void MarkOperator (MethodDefinition method) + { + _context.Annotations.Mark (method, new DependencyInfo (DependencyKind.PreservedOperator, method.DeclaringType)); + } + + bool ProcessCustomOperators (TypeDefinition type, bool mark) + { + if (!type.HasMethods) + return false; + + bool hasCustomOperators = false; + foreach (var method in type.Methods) { + if (!IsOperator (method, out var otherType)) + continue; + + if (!mark) + return true; + + Debug.Assert (_seenLinqExpressions); + hasCustomOperators = true; + + if (otherType == null || _context.Annotations.IsMarked (otherType)) { + MarkOperator (method); + continue; + } + + // Wait until otherType gets marked to mark the operator. + if (!PendingOperatorsForType.TryGetValue (otherType, out var pendingOperators)) { + pendingOperators = new List (); + PendingOperatorsForType.Add (otherType, pendingOperators); + } + pendingOperators.Add (method); + } + return hasCustomOperators; + } + + TypeDefinition _nullableOfT; + TypeDefinition NullableOfT { + get { + if (_nullableOfT == null) + _nullableOfT = BCL.FindPredefinedType ("System", "Nullable`1", _context); + return _nullableOfT; + } + } + + TypeDefinition NonNullableType (TypeReference type) + { + var typeDef = _context.TryResolve (type); + if (typeDef == null) + return null; + + if (!typeDef.IsValueType || typeDef != NullableOfT) + return typeDef; + + // Unwrap Nullable + Debug.Assert (typeDef.HasGenericParameters); + var nullableType = type as GenericInstanceType; + Debug.Assert (nullableType != null && nullableType.HasGenericArguments && nullableType.GenericArguments.Count == 1); + return _context.TryResolve (nullableType.GenericArguments[0]); + } + + bool IsOperator (MethodDefinition method, out TypeDefinition otherType) + { + otherType = null; + + if (!method.IsStatic || !method.IsPublic || !method.IsSpecialName || !method.Name.StartsWith ("op_")) + return false; + + var operatorName = method.Name.Substring (3); + var self = method.DeclaringType; + + switch (operatorName) { + // Unary operators + case "UnaryPlus": + case "UnaryNegation": + case "LogicalNot": + case "OnesComplement": + case "Increment": + case "Decrement": + case "True": + case "False": + // Parameter type of a unary operator must be the declaring type + if (method.Parameters.Count != 1 || NonNullableType (method.Parameters[0].ParameterType) != self) + return false; + // ++ and -- must return the declaring type + if (operatorName is "Increment" or "Decrement" && NonNullableType (method.ReturnType) != self) + return false; + return true; + // Binary operators + case "Addition": + case "Subtraction": + case "Multiply": + case "Division": + case "Modulus": + case "BitwiseAnd": + case "BitwiseOr": + case "ExclusiveOr": + case "LeftShift": + case "RightShift": + case "Equality": + case "Inequality": + case "LessThan": + case "GreaterThan": + case "LessThanOrEqual": + case "GreaterThanOrEqual": + if (method.Parameters.Count != 2) + return false; + var nnLeft = NonNullableType (method.Parameters[0].ParameterType); + var nnRight = NonNullableType (method.Parameters[1].ParameterType); + if (nnLeft == null || nnRight == null) + return false; + // << and >> must take the declaring type and int + if (operatorName is "LeftShift" or "RightShift" && (nnLeft != self || nnRight.MetadataType != MetadataType.Int32)) + return false; + // At least one argument must be the declaring type + if (nnLeft != self && nnRight != self) + return false; + if (nnLeft != self) + otherType = nnLeft; + if (nnRight != self) + otherType = nnRight; + return true; + // Conversion operators + case "Implicit": + case "Explicit": + if (method.Parameters.Count != 1) + return false; + var nnSource = NonNullableType (method.Parameters[0].ParameterType); + var nnTarget = NonNullableType (method.ReturnType); + // Exactly one of source/target must be the declaring type + if (nnSource == self == (nnTarget == self)) + return false; + otherType = nnSource == self ? nnTarget : nnSource; + return true; + default: + return false; + } + } + } +} \ No newline at end of file diff --git a/src/linker/Linker/DependencyInfo.cs b/src/linker/Linker/DependencyInfo.cs index c0f326e98baf..5ebaa2d81975 100644 --- a/src/linker/Linker/DependencyInfo.cs +++ b/src/linker/Linker/DependencyInfo.cs @@ -136,6 +136,8 @@ public enum DependencyKind XmlSerialized = 84, // entry type or member for XML serialization SerializedRecursiveType = 85, // recursive type kept due to serialization handling SerializedMember = 86, // field or property kept on a type for serialization + + PreservedOperator = 87 // operator method preserved on a type } public readonly struct DependencyInfo : IEquatable diff --git a/src/linker/Linker/Driver.cs b/src/linker/Linker/Driver.cs index 1ea5f0b96896..dd8cbac7dfb4 100644 --- a/src/linker/Linker/Driver.cs +++ b/src/linker/Linker/Driver.cs @@ -355,6 +355,12 @@ protected int SetupContext (ILogger customLogger = null) continue; + case "--disable-operator-discovery": + if (!GetBoolParam (token, l => context.DisableOperatorDiscovery = l)) + return -1; + + continue; + case "--ignore-descriptors": if (!GetBoolParam (token, l => context.IgnoreDescriptors = l)) return -1; @@ -732,6 +738,9 @@ protected int SetupContext (ILogger customLogger = null) if (!context.DisableSerializationDiscovery) p.MarkHandlers.Add (new DiscoverSerializationHandler ()); + if (!context.DisableOperatorDiscovery) + p.MarkHandlers.Add (new DiscoverOperatorsHandler ()); + foreach (string custom_step in custom_steps) { if (!AddCustomStep (p, custom_step)) return -1; diff --git a/src/linker/Linker/LinkContext.cs b/src/linker/Linker/LinkContext.cs index 58e3e0499dec..61a6b17e420b 100644 --- a/src/linker/Linker/LinkContext.cs +++ b/src/linker/Linker/LinkContext.cs @@ -123,6 +123,8 @@ public bool IgnoreUnresolved { public bool DisableSerializationDiscovery { get; set; } + public bool DisableOperatorDiscovery { get; set; } + public bool IgnoreDescriptors { get; set; } public bool IgnoreSubstitutions { get; set; } diff --git a/test/Mono.Linker.Tests.Cases/LinqExpressions/CanDisableOperatorDiscovery.cs b/test/Mono.Linker.Tests.Cases/LinqExpressions/CanDisableOperatorDiscovery.cs new file mode 100644 index 000000000000..8a8bf6c8f501 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/LinqExpressions/CanDisableOperatorDiscovery.cs @@ -0,0 +1,31 @@ +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.LinqExpressions +{ + [SetupLinkerArgument ("--disable-operator-discovery")] + public class CanDisableOperatorDiscovery + { + public static void Main () + { + var c = new CustomOperators (); + var expression = typeof (System.Linq.Expressions.Expression); + c = -c; + var t = typeof (TargetType); + } + + [KeptMember (".ctor()")] + class CustomOperators + { + [Kept] + public static CustomOperators operator - (CustomOperators c) => null; + + public static CustomOperators operator + (CustomOperators c) => null; + public static CustomOperators operator + (CustomOperators left, CustomOperators right) => null; + public static explicit operator TargetType (CustomOperators self) => null; + } + + [Kept] + class TargetType { } + } +} diff --git a/test/Mono.Linker.Tests.Cases/LinqExpressions/CanPreserveCustomOperators.cs b/test/Mono.Linker.Tests.Cases/LinqExpressions/CanPreserveCustomOperators.cs new file mode 100644 index 000000000000..d08b3265b15f --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/LinqExpressions/CanPreserveCustomOperators.cs @@ -0,0 +1,93 @@ +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.LinqExpressions +{ + public class CanPreserveCustomOperators + { + public static void Main () + { + var t = typeof (CustomOperators); + var expression = typeof (System.Linq.Expressions.Expression); + + var t3 = typeof (TargetTypeImplicit); + var t4 = typeof (SourceTypeImplicit); + var t5 = typeof (TargetTypeExplicit); + var t6 = typeof (SourceTypeExplicit); + } + + class CustomOperators + { + // Unary operators + [Kept] + public static CustomOperators operator + (CustomOperators c) => null; + [Kept] + public static CustomOperators operator - (CustomOperators c) => null; + [Kept] + public static CustomOperators operator ! (CustomOperators c) => null; + [Kept] + public static CustomOperators operator ~ (CustomOperators c) => null; + [Kept] + public static CustomOperators operator ++ (CustomOperators c) => null; + [Kept] + public static CustomOperators operator -- (CustomOperators c) => null; + [Kept] + public static bool operator true (CustomOperators c) => true; + [Kept] + public static bool operator false (CustomOperators c) => true; + + // Binary operators + [Kept] + public static CustomOperators operator + (CustomOperators left, CustomOperators right) => null; + [Kept] + public static CustomOperators operator - (CustomOperators left, CustomOperators right) => null; + [Kept] + public static CustomOperators operator * (CustomOperators left, CustomOperators right) => null; + [Kept] + public static CustomOperators operator / (CustomOperators left, CustomOperators right) => null; + [Kept] + public static CustomOperators operator % (CustomOperators left, CustomOperators right) => null; + [Kept] + public static CustomOperators operator & (CustomOperators left, CustomOperators right) => null; + [Kept] + public static CustomOperators operator | (CustomOperators left, CustomOperators right) => null; + [Kept] + public static CustomOperators operator ^ (CustomOperators left, CustomOperators right) => null; + [Kept] + public static CustomOperators operator << (CustomOperators value, int shift) => null; + [Kept] + public static CustomOperators operator >> (CustomOperators value, int shift) => null; + [Kept] + public static CustomOperators operator == (CustomOperators left, CustomOperators right) => null; + [Kept] + public static CustomOperators operator != (CustomOperators left, CustomOperators right) => null; + [Kept] + public static CustomOperators operator < (CustomOperators left, CustomOperators right) => null; + [Kept] + public static CustomOperators operator > (CustomOperators left, CustomOperators right) => null; + [Kept] + public static CustomOperators operator <= (CustomOperators left, CustomOperators right) => null; + [Kept] + public static CustomOperators operator >= (CustomOperators left, CustomOperators right) => null; + + // conversion operators + [Kept] + public static implicit operator TargetTypeImplicit (CustomOperators self) => null; + [Kept] + public static implicit operator CustomOperators (SourceTypeImplicit other) => null; + [Kept] + public static explicit operator TargetTypeExplicit (CustomOperators self) => null; + [Kept] + public static explicit operator CustomOperators (SourceTypeExplicit other) => null; + } + + [Kept] + class TargetTypeImplicit { } + [Kept] + class SourceTypeImplicit { } + [Kept] + class TargetTypeExplicit { } + [Kept] + class SourceTypeExplicit { } + } +} diff --git a/test/Mono.Linker.Tests.Cases/LinqExpressions/CanPreserveNullableCustomOperators.cs b/test/Mono.Linker.Tests.Cases/LinqExpressions/CanPreserveNullableCustomOperators.cs new file mode 100644 index 000000000000..efcdb747749c --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/LinqExpressions/CanPreserveNullableCustomOperators.cs @@ -0,0 +1,85 @@ +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.LinqExpressions +{ + [SetupLinkerArgument ("--used-attrs-only")] + public class CanPreserveNullableCustomOperators + { + public static void Main () + { + var expression = typeof (System.Linq.Expressions.Expression); + + var r = typeof (ReferenceTypeOperators); + var t1 = typeof (TargetReferenceType); + var t2 = typeof (SourceReferenceType); + + var s = typeof (ValueTypeOperators); + var t3 = typeof (AdditionValueType); + var t4 = typeof (TargetValueType); + var t5 = typeof (SourceValueType); + + var s2 = typeof (ValueTypeUnusedOperators); + } + + class ReferenceTypeOperators + { + [Kept] + public static ReferenceTypeOperators operator + (ReferenceTypeOperators? c) => null; + [Kept] + public static bool operator true (ReferenceTypeOperators? c) => true; + [Kept] + public static bool operator false (ReferenceTypeOperators? c) => true; + [Kept] + public static ReferenceTypeOperators? operator + (ReferenceTypeOperators? left, ReferenceTypeOperators? right) => null; + [Kept] + public static explicit operator TargetReferenceType (ReferenceTypeOperators? self) => null; + [Kept] + public static explicit operator ReferenceTypeOperators (SourceReferenceType? other) => null; + } + + [Kept] + class TargetReferenceType { } + [Kept] + class SourceReferenceType { } + + struct ValueTypeOperators + { + [Kept] + public static ValueTypeOperators operator + (ValueTypeOperators? c) => default (ValueTypeOperators); + [Kept] + public static ValueTypeOperators? operator - (ValueTypeOperators? c) => null; + [Kept] + public static bool operator true (ValueTypeOperators? c) => true; + [Kept] + public static bool operator false (ValueTypeOperators? c) => true; + [Kept] + public static ValueTypeOperators? operator + (ValueTypeOperators? left, ValueTypeOperators? right) => null; + [Kept] + public static ValueTypeOperators? operator + (ValueTypeOperators? left, AdditionValueType? right) => null; + [Kept] + public static explicit operator TargetValueType? (ValueTypeOperators? self) => null; + [Kept] + public static explicit operator ValueTypeOperators? (SourceValueType? other) => null; + } + + [Kept] + struct ValueTypeUnusedOperators + { + public static ValueTypeUnusedOperators? operator + (ValueTypeUnusedOperators? left, AdditionValueTypeUnused? right) => null; + public static explicit operator TargetValueTypeUnused? (ValueTypeUnusedOperators? self) => null; + public static explicit operator ValueTypeUnusedOperators? (SourceValueTypeUnused? other) => null; + } + + [Kept] + struct AdditionValueType { } + [Kept] + struct TargetValueType { } + [Kept] + struct SourceValueType { } + + struct AdditionValueTypeUnused { } + struct TargetValueTypeUnused { } + struct SourceValueTypeUnused { } + } +} diff --git a/test/Mono.Linker.Tests.Cases/LinqExpressions/CanRemoveMethodsNamedLikeCustomOperators.cs b/test/Mono.Linker.Tests.Cases/LinqExpressions/CanRemoveMethodsNamedLikeCustomOperators.cs new file mode 100644 index 000000000000..7196cfb8f44b --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/LinqExpressions/CanRemoveMethodsNamedLikeCustomOperators.cs @@ -0,0 +1,31 @@ +using Mono.Linker.Tests.Cases.Expectations.Assertions; + +namespace Mono.Linker.Tests.Cases.LinqExpressions +{ + public class CanRemoveMethodsNamedLikeCustomOperators + { + public static void Main () + { + var t = typeof (FakeOperators); + var expression = typeof (System.Linq.Expressions.Expression); + var t1 = typeof (SubtractionType); + var t2 = typeof (TargetType); + } + + public class FakeOperators + { + [Kept] + public static FakeOperators operator - (FakeOperators f) => null; + + public static FakeOperators op_UnaryPlus (FakeOperators f) => null; + public static FakeOperators op_Addition (FakeOperators left, FakeOperators right) => null; + public static FakeOperators op_Subtraction (FakeOperators left, SubtractionType right) => null; + public static TargetType op_Explicit (FakeOperators self) => null; + } + + [Kept] + public class SubtractionType { } + [Kept] + public class TargetType { } + } +} diff --git a/test/Mono.Linker.Tests.Cases/LinqExpressions/CanRemoveOperatorsWhenNotUsingLinqExpressions.cs b/test/Mono.Linker.Tests.Cases/LinqExpressions/CanRemoveOperatorsWhenNotUsingLinqExpressions.cs new file mode 100644 index 000000000000..ee4fc91f8d60 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/LinqExpressions/CanRemoveOperatorsWhenNotUsingLinqExpressions.cs @@ -0,0 +1,41 @@ +using Mono.Linker.Tests.Cases.Expectations.Assertions; + +namespace Mono.Linker.Tests.Cases.LinqExpressions +{ + public class CanRemoveOperatorsWhenNotUsingLinqExpressions + { + public static void Main () + { + var c = new CustomOperators (); + var c2 = +c; + var c3 = c + c2; + var t = (TargetType) c3; + } + + [Kept] + [KeptMember (".ctor()")] + class CustomOperators + { + [Kept] + public static CustomOperators operator + (CustomOperators c) => null; + public static CustomOperators operator - (CustomOperators c) => null; + + [Kept] + public static CustomOperators operator + (CustomOperators left, CustomOperators right) => null; + public static CustomOperators operator + (CustomOperators left, AdditionTypeUnused right) => null; + public static CustomOperators operator - (CustomOperators left, CustomOperators right) => null; + + [Kept] + public static explicit operator TargetType (CustomOperators self) => null; + + public static explicit operator CustomOperators (SourceTypeUnused other) => null; + } + + class AdditionTypeUnused { } + + [Kept] + class TargetType { } + + class SourceTypeUnused { } + } +} diff --git a/test/Mono.Linker.Tests.Cases/LinqExpressions/CustomOperatorsWithUnusedArgumentTypes.cs b/test/Mono.Linker.Tests.Cases/LinqExpressions/CustomOperatorsWithUnusedArgumentTypes.cs new file mode 100644 index 000000000000..15e96bc791b3 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/LinqExpressions/CustomOperatorsWithUnusedArgumentTypes.cs @@ -0,0 +1,57 @@ +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.LinqExpressions +{ + public class CustomOperatorsWithUnusedArgumentTypes + { + public static void Main () + { + var t = typeof (CustomOperators); + var expression = typeof (System.Linq.Expressions.Expression); + + var t1 = typeof (AdditionType); + var t2 = typeof (SubtractionType); + } + + public class CustomOperators + { + // simple cases are still kept + [Kept] + public static CustomOperators operator + (CustomOperators c) => null; + [Kept] + public static CustomOperators operator + (CustomOperators left, CustomOperators right) => null; + [Kept] + public static CustomOperators operator - (CustomOperators left, CustomOperators right) => null; + + // binary operators taking kept other types are still kept + [Kept] + public static CustomOperators operator + (CustomOperators left, AdditionType right) => null; + [Kept] + public static CustomOperators operator - (SubtractionType left, CustomOperators right) => null; + + // binary operators taking unused other types are removed + public static CustomOperators operator * (CustomOperators left, MultiplicationTypeUnused right) => null; + public static CustomOperators operator / (DivisionTypeUnused left, CustomOperators right) => null; + + // conversion operators to/from unused other types are removed + public static implicit operator TargetTypeImplicitUnused (CustomOperators self) => null; + public static implicit operator CustomOperators (SourceTypeImplicitUnused other) => null; + public static explicit operator TargetTypeExplicitUnused (CustomOperators self) => null; + public static explicit operator CustomOperators (SourceTypeExplicitUnused other) => null; + } + + [Kept] + public class AdditionType { } + [Kept] + public class SubtractionType { } + + public class MultiplicationTypeUnused { } + public class DivisionTypeUnused { } + + public class TargetTypeImplicitUnused { } + public class SourceTypeImplicitUnused { } + public class TargetTypeExplicitUnused { } + public class SourceTypeExplicitUnused { } + } +} diff --git a/test/Mono.Linker.Tests/TestCases/TestDatabase.cs b/test/Mono.Linker.Tests/TestCases/TestDatabase.cs index 58c0b9205b06..8179e1d2c9e6 100644 --- a/test/Mono.Linker.Tests/TestCases/TestDatabase.cs +++ b/test/Mono.Linker.Tests/TestCases/TestDatabase.cs @@ -216,6 +216,11 @@ public static IEnumerable XmlTests () return NUnitCasesBySuiteName ("LinkXml"); } + public static IEnumerable LinqExpressionsTests () + { + return NUnitCasesBySuiteName ("LinqExpressions"); + } + public static IEnumerable MetadataTests () { return NUnitCasesBySuiteName ("Metadata"); diff --git a/test/Mono.Linker.Tests/TestCases/TestSuites.cs b/test/Mono.Linker.Tests/TestCases/TestSuites.cs index 09de355e65a4..045d2a9e6acd 100644 --- a/test/Mono.Linker.Tests/TestCases/TestSuites.cs +++ b/test/Mono.Linker.Tests/TestCases/TestSuites.cs @@ -259,6 +259,12 @@ public void XmlTests (TestCase testCase) Run (testCase); } + [TestCaseSource (typeof (TestDatabase), nameof (TestDatabase.LinqExpressionsTests))] + public void LinqExpressionsTests (TestCase testCase) + { + Run (testCase); + } + [TestCaseSource (typeof (TestDatabase), nameof (TestDatabase.MetadataTests))] public void MetadataTests (TestCase testCase) {