Skip to content

Commit

Permalink
Resolve ILLink warnings in System.Linq.Expressions (Final)
Browse files Browse the repository at this point in the history
Suppress ILLink warnings for operator methods now that dotnet/linker#1821 is resolved.

Add TrimmingTests for Linq.Expressions operators.

Fix dotnet#45623
  • Loading branch information
eerhardt committed Jul 16, 2021
1 parent 0851fee commit bff849b
Show file tree
Hide file tree
Showing 10 changed files with 294 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -451,11 +451,8 @@ public abstract partial class Expression
public static System.Linq.Expressions.GotoExpression Continue(System.Linq.Expressions.LabelTarget target) { throw null; }
public static System.Linq.Expressions.GotoExpression Continue(System.Linq.Expressions.LabelTarget target, System.Type type) { throw null; }
public static System.Linq.Expressions.UnaryExpression Convert(System.Linq.Expressions.Expression expression, System.Type type) { throw null; }
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Creating Expressions requires unreferenced code because the members being referenced by the Expression may be trimmed.")]
public static System.Linq.Expressions.UnaryExpression Convert(System.Linq.Expressions.Expression expression, System.Type type, System.Reflection.MethodInfo? method) { throw null; }
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Creating Expressions requires unreferenced code because the members being referenced by the Expression may be trimmed.")]
public static System.Linq.Expressions.UnaryExpression ConvertChecked(System.Linq.Expressions.Expression expression, System.Type type) { throw null; }
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Creating Expressions requires unreferenced code because the members being referenced by the Expression may be trimmed.")]
public static System.Linq.Expressions.UnaryExpression ConvertChecked(System.Linq.Expressions.Expression expression, System.Type type, System.Reflection.MethodInfo? method) { throw null; }
public static System.Linq.Expressions.DebugInfoExpression DebugInfo(System.Linq.Expressions.SymbolDocumentInfo document, int startLine, int startColumn, int endLine, int endColumn) { throw null; }
public static System.Linq.Expressions.UnaryExpression Decrement(System.Linq.Expressions.Expression expression) { throw null; }
Expand Down Expand Up @@ -573,9 +570,7 @@ public abstract partial class Expression
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; }
public static System.Linq.Expressions.MemberExpression MakeMemberAccess(System.Linq.Expressions.Expression? expression, System.Reflection.MemberInfo member) { throw null; }
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; }
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Creating Expressions requires unreferenced code because the members being referenced by the Expression may be trimmed.")]
public static System.Linq.Expressions.UnaryExpression MakeUnary(System.Linq.Expressions.ExpressionType unaryType, System.Linq.Expressions.Expression operand, System.Type type) { throw null; }
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Creating Expressions requires unreferenced code because the members being referenced by the Expression may be trimmed.")]
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; }
public static System.Linq.Expressions.MemberMemberBinding MemberBind(System.Reflection.MemberInfo member, System.Collections.Generic.IEnumerable<System.Linq.Expressions.MemberBinding> bindings) { throw null; }
public static System.Linq.Expressions.MemberMemberBinding MemberBind(System.Reflection.MemberInfo member, params System.Linq.Expressions.MemberBinding[] bindings) { throw null; }
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;

namespace System.Dynamic.Utils
Expand All @@ -15,7 +16,10 @@ internal static class TypeExtensions
/// Returns the matching method if the parameter types are reference
/// assignable from the provided type arguments, otherwise null.
/// </summary>
public static MethodInfo? GetAnyStaticMethodValidated(this Type type, string name, Type[] types)
public static MethodInfo? GetAnyStaticMethodValidated(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] this Type type,
string name,
Type[] types)
{
Debug.Assert(types != null);
MethodInfo? method = type.GetMethod(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly, null, types, null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -623,7 +623,8 @@ public static bool HasBuiltInEqualityOperator(Type left, Type right)
|| IsImplicitBoxingConversion(source, destination)
|| IsImplicitNullableConversion(source, destination);

[RequiresUnreferencedCode(Expression.ExpressionRequiresUnreferencedCode)]
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern",
Justification = "The trimmer doesn't remove operators when System.Linq.Expressions is used. See https://github.com/mono/linker/issues/1821.")]
public static MethodInfo? GetUserDefinedCoercionMethod(Type convertFrom, Type convertToType)
{
Type nnExprType = GetNonNullableType(convertFrom);
Expand Down Expand Up @@ -829,8 +830,12 @@ private static bool IsImplicitNumericConversion(Type source, Type destination)
/// op_False, because we have to do runtime lookup for those. It may
/// not work right for unary operators in general.
/// </summary>
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2067:UnrecognizedReflectionPattern",
Justification = "The trimmer doesn't remove operators when System.Linq.Expressions is used. See https://github.com/mono/linker/issues/1821.")]
public static MethodInfo? GetBooleanOperator(Type type, string name)
{
Debug.Assert(name == "op_False" || name == "op_True");

do
{
MethodInfo? result = type.GetAnyStaticMethodValidated(name, new[] { type });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,8 @@ private static BinaryExpression GetUserDefinedAssignOperatorOrThrow(ExpressionTy
return b;
}

[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2072:UnrecognizedReflectionPattern",
Justification = "The trimmer doesn't remove operators when System.Linq.Expressions is used. See https://github.com/mono/linker/issues/1821.")]
private static MethodInfo? GetUserDefinedBinaryOperator(ExpressionType binaryType, Type leftType, Type rightType, string name)
{
// This algorithm is wrong, we should be checking for uniqueness and erroring if
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,8 +279,6 @@ private Expression ReduceIndex()
/// </summary>
/// <param name="operand">The <see cref="Operand"/> property of the result.</param>
/// <returns>This expression if no children changed, or an expression with the updated children.</returns>
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "A UnaryExpression has already been created. The original creator will get a warning that it is not trim compatible.")]
public UnaryExpression Update(Expression operand)
{
if (operand == Operand)
Expand All @@ -302,7 +300,6 @@ public partial class Expression
/// <returns>The <see cref="UnaryExpression"/> that results from calling the appropriate factory method.</returns>
/// <exception cref="ArgumentException">Thrown when <paramref name="unaryType"/> does not correspond to a unary expression.</exception>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="operand"/> is null.</exception>
[RequiresUnreferencedCode(ExpressionRequiresUnreferencedCode)]
public static UnaryExpression MakeUnary(ExpressionType unaryType, Expression operand, Type type)
{
return MakeUnary(unaryType, operand, type, method: null);
Expand All @@ -318,7 +315,6 @@ public static UnaryExpression MakeUnary(ExpressionType unaryType, Expression ope
/// <returns>The <see cref="UnaryExpression"/> that results from calling the appropriate factory method.</returns>
/// <exception cref="ArgumentException">Thrown when <paramref name="unaryType"/> does not correspond to a unary expression.</exception>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="operand"/> is null.</exception>
[RequiresUnreferencedCode(ExpressionRequiresUnreferencedCode)]
public static UnaryExpression MakeUnary(ExpressionType unaryType, Expression operand, Type type, MethodInfo? method) =>
unaryType switch
{
Expand Down Expand Up @@ -356,6 +352,8 @@ private static UnaryExpression GetUserDefinedUnaryOperatorOrThrow(ExpressionType
throw Error.UnaryOperatorNotDefined(unaryType, operand.Type);
}

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

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

[RequiresUnreferencedCode(Expression.ExpressionRequiresUnreferencedCode)]
private static UnaryExpression? GetUserDefinedCoercion(ExpressionType coercionType, Expression expression, Type convertToType)
{
MethodInfo? method = TypeUtils.GetUserDefinedCoercionMethod(expression.Type, convertToType);
Expand Down Expand Up @@ -746,7 +742,6 @@ public static UnaryExpression Convert(Expression expression, Type type)
/// <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>
/// <exception cref="AmbiguousMatchException">More than one method that matches the <paramref name="method"/> description was found.</exception>
/// <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>
[RequiresUnreferencedCode(ExpressionRequiresUnreferencedCode)]
public static UnaryExpression Convert(Expression expression, Type type, MethodInfo? method)
{
ExpressionUtils.RequiresCanRead(expression, nameof(expression));
Expand All @@ -771,7 +766,6 @@ public static UnaryExpression Convert(Expression expression, Type type, MethodIn
/// <exception cref="ArgumentNullException">
/// <paramref name="expression"/> or <paramref name="type"/> is null.</exception>
/// <exception cref="InvalidOperationException">No conversion operator is defined between <paramref name="expression"/>.Type and <paramref name="type"/>.</exception>
[RequiresUnreferencedCode(ExpressionRequiresUnreferencedCode)]
public static UnaryExpression ConvertChecked(Expression expression, Type type)
{
return ConvertChecked(expression, type, method: null);
Expand All @@ -788,7 +782,6 @@ public static UnaryExpression ConvertChecked(Expression expression, Type type)
/// <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>
/// <exception cref="AmbiguousMatchException">More than one method that matches the <paramref name="method"/> description was found.</exception>
/// <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>
[RequiresUnreferencedCode(ExpressionRequiresUnreferencedCode)]
public static UnaryExpression ConvertChecked(Expression expression, Type type, MethodInfo? method)
{
ExpressionUtils.RequiresCanRead(expression, nameof(expression));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Linq.Expressions;

/// <summary>
/// Tests that Expression.Add expressions still work correctly and find
/// the + operator in a trimmed app.
/// </summary>
internal class Program
{
static int Main(string[] args)
{
ParameterExpression leftParameter = Expression.Parameter(typeof(Class1));
ParameterExpression rightParameter = Expression.Parameter(typeof(Class1));
ParameterExpression result = Expression.Variable(typeof(Class1));

Func<Class1, Class1, Class1> func =
Expression.Lambda<Func<Class1, Class1, Class1>>(
Expression.Block(
new[] { result },
Expression.Assign(result, Expression.Add(leftParameter, rightParameter)),
result),
leftParameter, rightParameter)
.Compile();

Class1 actual = func(new Class1("left"), new Class1("right"));
if (actual.Name != "left+right")
{
return -1;
}

// make sure Class2 was trimmed since it wasn't used, even though Class1 has a binary operator using it
int i = 2;
if (typeof(Program).Assembly.GetType("Class" + i) != null)
{
return -2;
}

return 100;
}
}

internal class Class1
{
public Class1(string name) => Name = name;

public string Name { get; set; }

public static Class1 operator +(Class1 left, Class1 right) =>
new Class1($"{left.Name}+{right.Name}");

public static Class1 operator +(Class1 left, Class2 right) =>
new Class1($"{left.Name}+{right.Name}2");
public static Class2 operator +(Class2 left, Class1 right) =>
new Class2($"{left.Name}2+{right.Name}");
}

internal class Class2
{
public Class2(string name) => Name = name;

public string Name { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Linq.Expressions;
using System.Reflection;

/// <summary>
/// Tests that Expression.Convert expressions still work correctly and find
/// implicit and explicit operators in a trimmed app.
/// </summary>
internal class Program
{
static int Main(string[] args)
{
Type[] convertTypes = new Type[] { typeof(Class2), typeof(Class3) };

ParameterExpression class1Parameter = Expression.Parameter(typeof(Class1), "class1");
MethodInfo getNameMethodInfo = typeof(Program).GetMethod("GetName");
foreach (Type convertType in convertTypes)
{
UnaryExpression conversion = Expression.Convert(class1Parameter, convertType);

Func<Class1, string> getNameFunc =
Expression.Lambda<Func<Class1, string>>(
Expression.Call(null, getNameMethodInfo, conversion),
class1Parameter)
.Compile();

string name = getNameFunc(new Class1() { Name = convertType.Name });
if (convertType.Name == "Class2")
{
if (name != "Class2_implicit")
{
return -1;
}
}
else if (convertType.Name == "Class3")
{
if (name != "Class3_explicit")
{
return -2;
}
}
else
{
return -3;
}
}

// make sure Class4 was trimmed since it wasn't used, even though Class1 has a conversion operator to it
int i = 4;
if (typeof(Program).Assembly.GetType("Class" + i) != null)
{
return -4;
}

return 100;
}

public static string GetName(IHasName hasName) => hasName.Name;
}

interface IHasName
{
string Name { get; }
}

internal class Class1 : IHasName
{
public string Name { get; set; }

public static implicit operator Class2(Class1 class1) => new Class2() { Name = class1.Name + "_implicit" };
public static explicit operator Class3(Class1 class1) => new Class3() { Name = class1.Name + "_explicit" };
public static implicit operator Class4(Class1 class1) => new Class4() { Name = class1.Name + "_implicit" };
}

internal class Class2 : IHasName
{
public string Name { get; set; }
}

internal class Class3 : IHasName
{
public string Name { get; set; }
}

internal class Class4 : IHasName
{
public string Name { get; set; }
}
Loading

0 comments on commit bff849b

Please sign in to comment.