Skip to content

Commit a2f2a62

Browse files
authored
Resolve ILLink warnings in System.Linq.Expressions (Final) (#55856)
* Resolve ILLink warnings in System.Linq.Expressions (Final) Suppress ILLink warnings for operator methods now that dotnet/linker#1821 is resolved. Add TrimmingTests for Linq.Expressions operators. Fix #45623
1 parent b823f14 commit a2f2a62

File tree

10 files changed

+294
-33
lines changed

10 files changed

+294
-33
lines changed

src/libraries/System.Linq.Expressions/ref/System.Linq.Expressions.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -451,11 +451,8 @@ protected Expression(System.Linq.Expressions.ExpressionType nodeType, System.Typ
451451
public static System.Linq.Expressions.GotoExpression Continue(System.Linq.Expressions.LabelTarget target) { throw null; }
452452
public static System.Linq.Expressions.GotoExpression Continue(System.Linq.Expressions.LabelTarget target, System.Type type) { throw null; }
453453
public static System.Linq.Expressions.UnaryExpression Convert(System.Linq.Expressions.Expression expression, System.Type type) { throw null; }
454-
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Creating Expressions requires unreferenced code because the members being referenced by the Expression may be trimmed.")]
455454
public static System.Linq.Expressions.UnaryExpression Convert(System.Linq.Expressions.Expression expression, System.Type type, System.Reflection.MethodInfo? method) { throw null; }
456-
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Creating Expressions requires unreferenced code because the members being referenced by the Expression may be trimmed.")]
457455
public static System.Linq.Expressions.UnaryExpression ConvertChecked(System.Linq.Expressions.Expression expression, System.Type type) { throw null; }
458-
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Creating Expressions requires unreferenced code because the members being referenced by the Expression may be trimmed.")]
459456
public static System.Linq.Expressions.UnaryExpression ConvertChecked(System.Linq.Expressions.Expression expression, System.Type type, System.Reflection.MethodInfo? method) { throw null; }
460457
public static System.Linq.Expressions.DebugInfoExpression DebugInfo(System.Linq.Expressions.SymbolDocumentInfo document, int startLine, int startColumn, int endLine, int endColumn) { throw null; }
461458
public static System.Linq.Expressions.UnaryExpression Decrement(System.Linq.Expressions.Expression expression) { throw null; }
@@ -573,9 +570,7 @@ protected Expression(System.Linq.Expressions.ExpressionType nodeType, System.Typ
573570
public static System.Linq.Expressions.IndexExpression MakeIndex(System.Linq.Expressions.Expression instance, System.Reflection.PropertyInfo? indexer, System.Collections.Generic.IEnumerable<System.Linq.Expressions.Expression>? arguments) { throw null; }
574571
public static System.Linq.Expressions.MemberExpression MakeMemberAccess(System.Linq.Expressions.Expression? expression, System.Reflection.MemberInfo member) { throw null; }
575572
public static System.Linq.Expressions.TryExpression MakeTry(System.Type? type, System.Linq.Expressions.Expression body, System.Linq.Expressions.Expression? @finally, System.Linq.Expressions.Expression? fault, System.Collections.Generic.IEnumerable<System.Linq.Expressions.CatchBlock>? handlers) { throw null; }
576-
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Creating Expressions requires unreferenced code because the members being referenced by the Expression may be trimmed.")]
577573
public static System.Linq.Expressions.UnaryExpression MakeUnary(System.Linq.Expressions.ExpressionType unaryType, System.Linq.Expressions.Expression operand, System.Type type) { throw null; }
578-
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Creating Expressions requires unreferenced code because the members being referenced by the Expression may be trimmed.")]
579574
public static System.Linq.Expressions.UnaryExpression MakeUnary(System.Linq.Expressions.ExpressionType unaryType, System.Linq.Expressions.Expression operand, System.Type type, System.Reflection.MethodInfo? method) { throw null; }
580575
public static System.Linq.Expressions.MemberMemberBinding MemberBind(System.Reflection.MemberInfo member, System.Collections.Generic.IEnumerable<System.Linq.Expressions.MemberBinding> bindings) { throw null; }
581576
public static System.Linq.Expressions.MemberMemberBinding MemberBind(System.Reflection.MemberInfo member, params System.Linq.Expressions.MemberBinding[] bindings) { throw null; }

src/libraries/System.Linq.Expressions/src/ILLink/ILLink.Suppressions.xml

Lines changed: 0 additions & 17 deletions
This file was deleted.

src/libraries/System.Linq.Expressions/src/System/Dynamic/Utils/TypeExtensions.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Diagnostics;
5+
using System.Diagnostics.CodeAnalysis;
56
using System.Reflection;
67

78
namespace System.Dynamic.Utils
@@ -15,7 +16,10 @@ internal static class TypeExtensions
1516
/// Returns the matching method if the parameter types are reference
1617
/// assignable from the provided type arguments, otherwise null.
1718
/// </summary>
18-
public static MethodInfo? GetAnyStaticMethodValidated(this Type type, string name, Type[] types)
19+
public static MethodInfo? GetAnyStaticMethodValidated(
20+
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] this Type type,
21+
string name,
22+
Type[] types)
1923
{
2024
Debug.Assert(types != null);
2125
MethodInfo? method = type.GetMethod(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly, null, types, null);

src/libraries/System.Linq.Expressions/src/System/Dynamic/Utils/TypeUtils.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,8 @@ public static bool IsImplicitlyConvertibleTo(this Type source, Type destination)
623623
|| IsImplicitBoxingConversion(source, destination)
624624
|| IsImplicitNullableConversion(source, destination);
625625

626-
[RequiresUnreferencedCode(Expression.ExpressionRequiresUnreferencedCode)]
626+
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern",
627+
Justification = "The trimmer doesn't remove operators when System.Linq.Expressions is used. See https://github.com/mono/linker/pull/2125.")]
627628
public static MethodInfo? GetUserDefinedCoercionMethod(Type convertFrom, Type convertToType)
628629
{
629630
Type nnExprType = GetNonNullableType(convertFrom);
@@ -829,8 +830,12 @@ private static bool IsImplicitNullableConversion(Type source, Type destination)
829830
/// op_False, because we have to do runtime lookup for those. It may
830831
/// not work right for unary operators in general.
831832
/// </summary>
833+
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2067:UnrecognizedReflectionPattern",
834+
Justification = "The trimmer doesn't remove operators when System.Linq.Expressions is used. See https://github.com/mono/linker/pull/2125.")]
832835
public static MethodInfo? GetBooleanOperator(Type type, string name)
833836
{
837+
Debug.Assert(name == "op_False" || name == "op_True");
838+
834839
do
835840
{
836841
MethodInfo? result = type.GetAnyStaticMethodValidated(name, new[] { type });

src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/BinaryExpression.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,8 @@ private static BinaryExpression GetUserDefinedAssignOperatorOrThrow(ExpressionTy
710710
return b;
711711
}
712712

713+
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2072:UnrecognizedReflectionPattern",
714+
Justification = "The trimmer doesn't remove operators when System.Linq.Expressions is used. See https://github.com/mono/linker/pull/2125.")]
713715
private static MethodInfo? GetUserDefinedBinaryOperator(ExpressionType binaryType, Type leftType, Type rightType, string name)
714716
{
715717
// This algorithm is wrong, we should be checking for uniqueness and erroring if

src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/UnaryExpression.cs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -279,8 +279,6 @@ private Expression ReduceIndex()
279279
/// </summary>
280280
/// <param name="operand">The <see cref="Operand"/> property of the result.</param>
281281
/// <returns>This expression if no children changed, or an expression with the updated children.</returns>
282-
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
283-
Justification = "A UnaryExpression has already been created. The original creator will get a warning that it is not trim compatible.")]
284282
public UnaryExpression Update(Expression operand)
285283
{
286284
if (operand == Operand)
@@ -302,7 +300,6 @@ public partial class Expression
302300
/// <returns>The <see cref="UnaryExpression"/> that results from calling the appropriate factory method.</returns>
303301
/// <exception cref="ArgumentException">Thrown when <paramref name="unaryType"/> does not correspond to a unary expression.</exception>
304302
/// <exception cref="ArgumentNullException">Thrown when <paramref name="operand"/> is null.</exception>
305-
[RequiresUnreferencedCode(ExpressionRequiresUnreferencedCode)]
306303
public static UnaryExpression MakeUnary(ExpressionType unaryType, Expression operand, Type type)
307304
{
308305
return MakeUnary(unaryType, operand, type, method: null);
@@ -318,7 +315,6 @@ public static UnaryExpression MakeUnary(ExpressionType unaryType, Expression ope
318315
/// <returns>The <see cref="UnaryExpression"/> that results from calling the appropriate factory method.</returns>
319316
/// <exception cref="ArgumentException">Thrown when <paramref name="unaryType"/> does not correspond to a unary expression.</exception>
320317
/// <exception cref="ArgumentNullException">Thrown when <paramref name="operand"/> is null.</exception>
321-
[RequiresUnreferencedCode(ExpressionRequiresUnreferencedCode)]
322318
public static UnaryExpression MakeUnary(ExpressionType unaryType, Expression operand, Type type, MethodInfo? method) =>
323319
unaryType switch
324320
{
@@ -356,6 +352,8 @@ private static UnaryExpression GetUserDefinedUnaryOperatorOrThrow(ExpressionType
356352
throw Error.UnaryOperatorNotDefined(unaryType, operand.Type);
357353
}
358354

355+
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2072:UnrecognizedReflectionPattern",
356+
Justification = "The trimmer doesn't remove operators when System.Linq.Expressions is used. See https://github.com/mono/linker/pull/2125.")]
359357
private static UnaryExpression? GetUserDefinedUnaryOperator(ExpressionType unaryType, string name, Expression operand)
360358
{
361359
Type operandType = operand.Type;
@@ -402,7 +400,6 @@ private static UnaryExpression GetMethodBasedUnaryOperator(ExpressionType unaryT
402400
throw Error.OperandTypesDoNotMatchParameters(unaryType, method.Name);
403401
}
404402

405-
[RequiresUnreferencedCode(Expression.ExpressionRequiresUnreferencedCode)]
406403
private static UnaryExpression GetUserDefinedCoercionOrThrow(ExpressionType coercionType, Expression expression, Type convertToType)
407404
{
408405
UnaryExpression? u = GetUserDefinedCoercion(coercionType, expression, convertToType);
@@ -413,7 +410,6 @@ private static UnaryExpression GetUserDefinedCoercionOrThrow(ExpressionType coer
413410
throw Error.CoercionOperatorNotDefined(expression.Type, convertToType);
414411
}
415412

416-
[RequiresUnreferencedCode(Expression.ExpressionRequiresUnreferencedCode)]
417413
private static UnaryExpression? GetUserDefinedCoercion(ExpressionType coercionType, Expression expression, Type convertToType)
418414
{
419415
MethodInfo? method = TypeUtils.GetUserDefinedCoercionMethod(expression.Type, convertToType);
@@ -746,7 +742,6 @@ public static UnaryExpression Convert(Expression expression, Type type)
746742
/// <paramref name="method"/> is not null and the method it represents returns void, is not static (Shared in Visual Basic), or does not take exactly one argument.</exception>
747743
/// <exception cref="AmbiguousMatchException">More than one method that matches the <paramref name="method"/> description was found.</exception>
748744
/// <exception cref="InvalidOperationException">No conversion operator is defined between <paramref name="expression"/>.Type and <paramref name="type"/>.-or-<paramref name="expression"/>.Type is not assignable to the argument type of the method represented by <paramref name="method"/>.-or-The return type of the method represented by <paramref name="method"/> is not assignable to <paramref name="type"/>.-or-<paramref name="expression"/>.Type or <paramref name="type"/> is a nullable value type and the corresponding non-nullable value type does not equal the argument type or the return type, respectively, of the method represented by <paramref name="method"/>.</exception>
749-
[RequiresUnreferencedCode(ExpressionRequiresUnreferencedCode)]
750745
public static UnaryExpression Convert(Expression expression, Type type, MethodInfo? method)
751746
{
752747
ExpressionUtils.RequiresCanRead(expression, nameof(expression));
@@ -771,7 +766,6 @@ public static UnaryExpression Convert(Expression expression, Type type, MethodIn
771766
/// <exception cref="ArgumentNullException">
772767
/// <paramref name="expression"/> or <paramref name="type"/> is null.</exception>
773768
/// <exception cref="InvalidOperationException">No conversion operator is defined between <paramref name="expression"/>.Type and <paramref name="type"/>.</exception>
774-
[RequiresUnreferencedCode(ExpressionRequiresUnreferencedCode)]
775769
public static UnaryExpression ConvertChecked(Expression expression, Type type)
776770
{
777771
return ConvertChecked(expression, type, method: null);
@@ -788,7 +782,6 @@ public static UnaryExpression ConvertChecked(Expression expression, Type type)
788782
/// <paramref name="method"/> is not null and the method it represents returns void, is not static (Shared in Visual Basic), or does not take exactly one argument.</exception>
789783
/// <exception cref="AmbiguousMatchException">More than one method that matches the <paramref name="method"/> description was found.</exception>
790784
/// <exception cref="InvalidOperationException">No conversion operator is defined between <paramref name="expression"/>.Type and <paramref name="type"/>.-or-<paramref name="expression"/>.Type is not assignable to the argument type of the method represented by <paramref name="method"/>.-or-The return type of the method represented by <paramref name="method"/> is not assignable to <paramref name="type"/>.-or-<paramref name="expression"/>.Type or <paramref name="type"/> is a nullable value type and the corresponding non-nullable value type does not equal the argument type or the return type, respectively, of the method represented by <paramref name="method"/>.</exception>
791-
[RequiresUnreferencedCode(ExpressionRequiresUnreferencedCode)]
792785
public static UnaryExpression ConvertChecked(Expression expression, Type type, MethodInfo? method)
793786
{
794787
ExpressionUtils.RequiresCanRead(expression, nameof(expression));
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Linq.Expressions;
6+
7+
/// <summary>
8+
/// Tests that Expression.Add expressions still work correctly and find
9+
/// the + operator in a trimmed app.
10+
/// </summary>
11+
internal class Program
12+
{
13+
static int Main(string[] args)
14+
{
15+
ParameterExpression leftParameter = Expression.Parameter(typeof(Class1));
16+
ParameterExpression rightParameter = Expression.Parameter(typeof(Class1));
17+
ParameterExpression result = Expression.Variable(typeof(Class1));
18+
19+
Func<Class1, Class1, Class1> func =
20+
Expression.Lambda<Func<Class1, Class1, Class1>>(
21+
Expression.Block(
22+
new[] { result },
23+
Expression.Assign(result, Expression.Add(leftParameter, rightParameter)),
24+
result),
25+
leftParameter, rightParameter)
26+
.Compile();
27+
28+
Class1 actual = func(new Class1("left"), new Class1("right"));
29+
if (actual.Name != "left+right")
30+
{
31+
return -1;
32+
}
33+
34+
// make sure Class2 was trimmed since it wasn't used, even though Class1 has a binary operator using it
35+
int i = 2;
36+
if (typeof(Program).Assembly.GetType("Class" + i) != null)
37+
{
38+
return -2;
39+
}
40+
41+
return 100;
42+
}
43+
}
44+
45+
internal class Class1
46+
{
47+
public Class1(string name) => Name = name;
48+
49+
public string Name { get; set; }
50+
51+
public static Class1 operator +(Class1 left, Class1 right) =>
52+
new Class1($"{left.Name}+{right.Name}");
53+
54+
public static Class1 operator +(Class1 left, Class2 right) =>
55+
new Class1($"{left.Name}+{right.Name}2");
56+
public static Class2 operator +(Class2 left, Class1 right) =>
57+
new Class2($"{left.Name}2+{right.Name}");
58+
}
59+
60+
internal class Class2
61+
{
62+
public Class2(string name) => Name = name;
63+
64+
public string Name { get; set; }
65+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Linq.Expressions;
6+
using System.Reflection;
7+
8+
/// <summary>
9+
/// Tests that Expression.Convert expressions still work correctly and find
10+
/// implicit and explicit operators in a trimmed app.
11+
/// </summary>
12+
internal class Program
13+
{
14+
static int Main(string[] args)
15+
{
16+
Type[] convertTypes = new Type[] { typeof(Class2), typeof(Class3) };
17+
18+
ParameterExpression class1Parameter = Expression.Parameter(typeof(Class1), "class1");
19+
MethodInfo getNameMethodInfo = typeof(Program).GetMethod("GetName");
20+
foreach (Type convertType in convertTypes)
21+
{
22+
UnaryExpression conversion = Expression.Convert(class1Parameter, convertType);
23+
24+
Func<Class1, string> getNameFunc =
25+
Expression.Lambda<Func<Class1, string>>(
26+
Expression.Call(null, getNameMethodInfo, conversion),
27+
class1Parameter)
28+
.Compile();
29+
30+
string name = getNameFunc(new Class1() { Name = convertType.Name });
31+
if (convertType.Name == "Class2")
32+
{
33+
if (name != "Class2_implicit")
34+
{
35+
return -1;
36+
}
37+
}
38+
else if (convertType.Name == "Class3")
39+
{
40+
if (name != "Class3_explicit")
41+
{
42+
return -2;
43+
}
44+
}
45+
else
46+
{
47+
return -3;
48+
}
49+
}
50+
51+
// make sure Class4 was trimmed since it wasn't used, even though Class1 has a conversion operator to it
52+
int i = 4;
53+
if (typeof(Program).Assembly.GetType("Class" + i) != null)
54+
{
55+
return -4;
56+
}
57+
58+
return 100;
59+
}
60+
61+
public static string GetName(IHasName hasName) => hasName.Name;
62+
}
63+
64+
interface IHasName
65+
{
66+
string Name { get; }
67+
}
68+
69+
internal class Class1 : IHasName
70+
{
71+
public string Name { get; set; }
72+
73+
public static implicit operator Class2(Class1 class1) => new Class2() { Name = class1.Name + "_implicit" };
74+
public static explicit operator Class3(Class1 class1) => new Class3() { Name = class1.Name + "_explicit" };
75+
public static implicit operator Class4(Class1 class1) => new Class4() { Name = class1.Name + "_implicit" };
76+
}
77+
78+
internal class Class2 : IHasName
79+
{
80+
public string Name { get; set; }
81+
}
82+
83+
internal class Class3 : IHasName
84+
{
85+
public string Name { get; set; }
86+
}
87+
88+
internal class Class4 : IHasName
89+
{
90+
public string Name { get; set; }
91+
}

0 commit comments

Comments
 (0)