Browse Source

[nullables] Support lifting calls to user-defined operators.

Not yet handled: op_Equality and op_Inequality.
pull/873/merge
Daniel Grunwald 8 years ago
parent
commit
b5e8571382
  1. 120
      ICSharpCode.Decompiler/CSharp/Resolver/CSharpOperators.cs
  2. 95
      ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs
  3. 9
      ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolution.cs
  4. 19
      ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs
  5. 99
      ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs

120
ICSharpCode.Decompiler/CSharp/Resolver/CSharpOperators.cs

@ -20,6 +20,7 @@ using System; @@ -20,6 +20,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ICSharpCode.Decompiler.CSharp.Syntax;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.TypeSystem.Implementation;
using ICSharpCode.Decompiler.Util;
@ -318,7 +319,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -318,7 +319,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
}
}
sealed class LiftedUnaryOperatorMethod : UnaryOperatorMethod, OverloadResolution.ILiftedOperator
sealed class LiftedUnaryOperatorMethod : UnaryOperatorMethod, ILiftedOperator
{
UnaryOperatorMethod baseMethod;
@ -329,9 +330,8 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -329,9 +330,8 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
this.Parameters.Add(operators.MakeNullableParameter(baseMethod.Parameters[0]));
}
public IList<IParameter> NonLiftedParameters {
get { return baseMethod.Parameters; }
}
public IList<IParameter> NonLiftedParameters => baseMethod.Parameters;
public IType NonLiftedReturnType => baseMethod.ReturnType;
}
#endregion
@ -484,7 +484,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -484,7 +484,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
}
}
sealed class LiftedBinaryOperatorMethod : BinaryOperatorMethod, OverloadResolution.ILiftedOperator
sealed class LiftedBinaryOperatorMethod : BinaryOperatorMethod, ILiftedOperator
{
readonly BinaryOperatorMethod baseMethod;
@ -497,9 +497,8 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -497,9 +497,8 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
this.Parameters.Add(operators.MakeNullableParameter(baseMethod.Parameters[1]));
}
public IList<IParameter> NonLiftedParameters {
get { return baseMethod.Parameters; }
}
public IList<IParameter> NonLiftedParameters => baseMethod.Parameters;
public IType NonLiftedReturnType => baseMethod.ReturnType;
}
#endregion
@ -728,7 +727,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -728,7 +727,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
}
}
sealed class LiftedEqualityOperatorMethod : BinaryOperatorMethod, OverloadResolution.ILiftedOperator
sealed class LiftedEqualityOperatorMethod : BinaryOperatorMethod, ILiftedOperator
{
readonly EqualityOperatorMethod baseMethod;
@ -751,9 +750,8 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -751,9 +750,8 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
return baseMethod.Invoke(resolver, lhs, rhs);
}
public IList<IParameter> NonLiftedParameters {
get { return baseMethod.Parameters; }
}
public IList<IParameter> NonLiftedParameters => baseMethod.Parameters;
public IType NonLiftedReturnType => baseMethod.ReturnType;
}
// C# 4.0 spec: §7.10 Relational and type-testing operators
@ -1041,5 +1039,103 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -1041,5 +1039,103 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
}
}
#endregion
#region User-defined operators
public static IMethod LiftUserDefinedOperator(IMethod m)
{
if (IsComparisonOperator(m)) {
if (!m.ReturnType.IsKnownType(KnownTypeCode.Boolean))
return null; // cannot lift this operator
} else {
if (!NullableType.IsNonNullableValueType(m.ReturnType))
return null; // cannot lift this operator
}
for (int i = 0; i < m.Parameters.Count; i++) {
if (!NullableType.IsNonNullableValueType(m.Parameters[i].Type))
return null; // cannot lift this operator
}
return new LiftedUserDefinedOperator(m);
}
internal static bool IsComparisonOperator(IMethod m)
{
return m.IsOperator && m.Parameters.Count == 2
&& (OperatorDeclaration.GetOperatorType(m.Name)?.IsComparisonOperator() ?? false);
}
sealed class LiftedUserDefinedOperator : SpecializedMethod, ILiftedOperator
{
internal readonly IParameterizedMember nonLiftedOperator;
public LiftedUserDefinedOperator(IMethod nonLiftedMethod)
: base((IMethod)nonLiftedMethod.MemberDefinition, nonLiftedMethod.Substitution)
{
this.nonLiftedOperator = nonLiftedMethod;
var substitution = new MakeNullableVisitor(nonLiftedMethod.Compilation, nonLiftedMethod.Substitution);
this.Parameters = base.CreateParameters(substitution);
// Comparison operators keep the 'bool' return type even when lifted.
if (IsComparisonOperator(nonLiftedMethod))
this.ReturnType = nonLiftedMethod.ReturnType;
else
this.ReturnType = nonLiftedMethod.ReturnType.AcceptVisitor(substitution);
}
public IList<IParameter> NonLiftedParameters => nonLiftedOperator.Parameters;
public IType NonLiftedReturnType => nonLiftedOperator.ReturnType;
public override bool Equals(object obj)
{
LiftedUserDefinedOperator op = obj as LiftedUserDefinedOperator;
return op != null && this.nonLiftedOperator.Equals(op.nonLiftedOperator);
}
public override int GetHashCode()
{
return nonLiftedOperator.GetHashCode() ^ 0x7191254;
}
}
sealed class MakeNullableVisitor : TypeVisitor
{
readonly ICompilation compilation;
readonly TypeParameterSubstitution typeParameterSubstitution;
public MakeNullableVisitor(ICompilation compilation, TypeParameterSubstitution typeParameterSubstitution)
{
this.compilation = compilation;
this.typeParameterSubstitution = typeParameterSubstitution;
}
public override IType VisitTypeDefinition(ITypeDefinition type)
{
return NullableType.Create(compilation, type.AcceptVisitor(typeParameterSubstitution));
}
public override IType VisitTypeParameter(ITypeParameter type)
{
return NullableType.Create(compilation, type.AcceptVisitor(typeParameterSubstitution));
}
public override IType VisitParameterizedType(ParameterizedType type)
{
return NullableType.Create(compilation, type.AcceptVisitor(typeParameterSubstitution));
}
public override IType VisitOtherType(IType type)
{
return NullableType.Create(compilation, type.AcceptVisitor(typeParameterSubstitution));
}
}
#endregion
}
/// <summary>
/// Implement this interface to give overload resolution a hint that the member represents a lifted operator,
/// which is used in the tie-breaking rules.
/// </summary>
public interface ILiftedOperator : IParameterizedMember
{
IType NonLiftedReturnType { get; }
IList<IParameter> NonLiftedParameters { get; }
}
}

95
ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs

@ -506,7 +506,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -506,7 +506,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
} else {
expression = Convert(expression, m.Parameters[0].Type, builtinOperatorOR.ArgumentConversions[0]);
return UnaryOperatorResolveResult(resultType, op, expression,
builtinOperatorOR.BestCandidate is OverloadResolution.ILiftedOperator);
builtinOperatorOR.BestCandidate is ILiftedOperator);
}
}
@ -876,7 +876,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -876,7 +876,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
lhs = Convert(lhs, m.Parameters[0].Type, builtinOperatorOR.ArgumentConversions[0]);
rhs = Convert(rhs, m.Parameters[1].Type, builtinOperatorOR.ArgumentConversions[1]);
return BinaryOperatorResolveResult(resultType, lhs, op, rhs,
builtinOperatorOR.BestCandidate is OverloadResolution.ILiftedOperator);
builtinOperatorOR.BestCandidate is ILiftedOperator);
}
}
@ -1166,106 +1166,19 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -1166,106 +1166,19 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
int nonLiftedMethodCount = operators.Count;
// Construct lifted operators
for (int i = 0; i < nonLiftedMethodCount; i++) {
var liftedMethod = LiftUserDefinedOperator(operators[i]);
var liftedMethod = CSharpOperators.LiftUserDefinedOperator(operators[i]);
if (liftedMethod != null)
operators.Add(liftedMethod);
}
}
LiftedUserDefinedOperator LiftUserDefinedOperator(IMethod m)
{
if (IsComparisonOperator(m)) {
if (!m.ReturnType.Equals(compilation.FindType(KnownTypeCode.Boolean)))
return null; // cannot lift this operator
} else {
if (!NullableType.IsNonNullableValueType(m.ReturnType))
return null; // cannot lift this operator
}
for (int i = 0; i < m.Parameters.Count; i++) {
if (!NullableType.IsNonNullableValueType(m.Parameters[i].Type))
return null; // cannot lift this operator
}
return new LiftedUserDefinedOperator(m);
}
static bool IsComparisonOperator(IMethod m)
{
var type = OperatorDeclaration.GetOperatorType(m.Name);
return type.HasValue && type.Value.IsComparisonOperator();
}
sealed class LiftedUserDefinedOperator : SpecializedMethod, OverloadResolution.ILiftedOperator
{
internal readonly IParameterizedMember nonLiftedOperator;
public LiftedUserDefinedOperator(IMethod nonLiftedMethod)
: base((IMethod)nonLiftedMethod.MemberDefinition, nonLiftedMethod.Substitution)
{
this.nonLiftedOperator = nonLiftedMethod;
var substitution = new MakeNullableVisitor(nonLiftedMethod.Compilation, nonLiftedMethod.Substitution);
this.Parameters = base.CreateParameters(substitution);
// Comparison operators keep the 'bool' return type even when lifted.
if (IsComparisonOperator(nonLiftedMethod))
this.ReturnType = nonLiftedMethod.ReturnType;
else
this.ReturnType = nonLiftedMethod.ReturnType.AcceptVisitor(substitution);
}
public IList<IParameter> NonLiftedParameters {
get { return nonLiftedOperator.Parameters; }
}
public override bool Equals(object obj)
{
LiftedUserDefinedOperator op = obj as LiftedUserDefinedOperator;
return op != null && this.nonLiftedOperator.Equals(op.nonLiftedOperator);
}
public override int GetHashCode()
{
return nonLiftedOperator.GetHashCode() ^ 0x7191254;
}
}
sealed class MakeNullableVisitor : TypeVisitor
{
readonly ICompilation compilation;
readonly TypeParameterSubstitution typeParameterSubstitution;
public MakeNullableVisitor(ICompilation compilation, TypeParameterSubstitution typeParameterSubstitution)
{
this.compilation = compilation;
this.typeParameterSubstitution = typeParameterSubstitution;
}
public override IType VisitTypeDefinition(ITypeDefinition type)
{
return NullableType.Create(compilation, type.AcceptVisitor(typeParameterSubstitution));
}
public override IType VisitTypeParameter(ITypeParameter type)
{
return NullableType.Create(compilation, type.AcceptVisitor(typeParameterSubstitution));
}
public override IType VisitParameterizedType(ParameterizedType type)
{
return NullableType.Create(compilation, type.AcceptVisitor(typeParameterSubstitution));
}
public override IType VisitOtherType(IType type)
{
return NullableType.Create(compilation, type.AcceptVisitor(typeParameterSubstitution));
}
}
ResolveResult CreateResolveResultForUserDefinedOperator(OverloadResolution r, System.Linq.Expressions.ExpressionType operatorType)
{
if (r.BestCandidateErrors != OverloadResolutionErrors.None)
return r.CreateResolveResult(null);
IMethod method = (IMethod)r.BestCandidate;
return new OperatorResolveResult(method.ReturnType, operatorType, method,
isLiftedOperator: method is OverloadResolution.ILiftedOperator,
isLiftedOperator: method is ILiftedOperator,
operands: r.GetArgumentsWithConversions());
}
#endregion

9
ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolution.cs

@ -684,15 +684,6 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -684,15 +684,6 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
return 0;
}
/// <summary>
/// Implement this interface to give overload resolution a hint that the member represents a lifted operator,
/// which is used in the tie-breaking rules.
/// </summary>
public interface ILiftedOperator : IParameterizedMember
{
IList<IParameter> NonLiftedParameters { get; }
}
int MoreSpecificFormalParameters(Candidate c1, Candidate c2)
{
// prefer the member with more formal parmeters (in case both have different number of optional parameters)

19
ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs

@ -103,4 +103,23 @@ namespace ICSharpCode.Decompiler.IL @@ -103,4 +103,23 @@ namespace ICSharpCode.Decompiler.IL
&& Patterns.ListMatch.DoMatch(this.Arguments, o.Arguments, ref match);
}
}
partial class Call : ILiftableInstruction
{
/// <summary>
/// Calls can only be lifted when calling a lifted operator.
/// Note that the semantics of such a lifted call depend on the type of operator:
/// we follow C# semantics here.
/// </summary>
public bool IsLifted => Method is CSharp.Resolver.ILiftedOperator;
public StackType UnderlyingResultType {
get {
if (Method is CSharp.Resolver.ILiftedOperator liftedOp)
return liftedOp.NonLiftedReturnType.GetStackType();
else
return Method.ReturnType.GetStackType();
}
}
}
}

99
ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs

@ -317,22 +317,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -317,22 +317,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// </summary>
Comp LiftCSharpComparison(Comp comp, ComparisonKind newComparisonKind)
{
var (left, leftBits) = DoLift(comp.Left);
var (right, rightBits) = DoLift(comp.Right);
if (left != null && right == null && SemanticHelper.IsPure(comp.Right.Flags)) {
// Embed non-nullable pure expression in lifted expression.
right = comp.Right.Clone();
}
if (left == null && right != null && SemanticHelper.IsPure(comp.Left.Flags)) {
// Embed non-nullable pure expression in lifted expression.
left = comp.Left.Clone();
}
var (left, right, bits) = DoLiftBinary(comp.Left, comp.Right);
// due to the restrictions on side effects, we only allow instructions that are pure after lifting.
// (we can't check this before lifting due to the calls to GetValueOrDefault())
if (left != null && right != null && SemanticHelper.IsPure(left.Flags) && SemanticHelper.IsPure(right.Flags)) {
var bits = leftBits ?? rightBits;
if (rightBits != null)
bits.UnionWith(rightBits);
if (!bits.All(0, nullableVars.Count)) {
// don't lift if a nullableVar doesn't contribute to the result
return null;
@ -366,6 +354,25 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -366,6 +354,25 @@ namespace ICSharpCode.Decompiler.IL.Transforms
UnderlyingResultType = NullableType.GetUnderlyingType(nullableVars[0].Type).GetStackType(),
ILRange = ilrange
};
} else if (trueInst is Call call && !call.IsLifted
&& CSharp.Resolver.CSharpOperators.IsComparisonOperator(call.Method)
&& call.Method.Name != "op_Equality" && call.Method.Name != "op_Inequality"
&& falseInst.MatchLdcI4(0))
{
// (v1 != null && ... && vn != null) ? call op_LessThan(lhs, rhs) : ldc.i4(0)
var liftedOperator = CSharp.Resolver.CSharpOperators.LiftUserDefinedOperator(call.Method);
if (liftedOperator != null) {
var (left, right, bits) = DoLiftBinary(call.Arguments[0], call.Arguments[1]);
if (left != null && right != null && bits.All(0, nullableVars.Count)) {
return new Call(liftedOperator) {
Arguments = { left, right },
ConstrainedTo = call.ConstrainedTo,
ILRange = call.ILRange,
ILStackWasEmpty = call.ILStackWasEmpty,
IsTail = call.IsTail
};
}
}
}
}
ILInstruction lifted;
@ -475,20 +482,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -475,20 +482,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return (newInst, bits);
}
} else if (inst is BinaryNumericInstruction binary) {
var (left, leftBits) = DoLift(binary.Left);
var (right, rightBits) = DoLift(binary.Right);
if (left != null && right == null && SemanticHelper.IsPure(binary.Right.Flags)) {
// Embed non-nullable pure expression in lifted expression.
right = binary.Right.Clone();
}
if (left == null && right != null && SemanticHelper.IsPure(binary.Left.Flags)) {
// Embed non-nullable pure expression in lifted expression.
left = binary.Left.Clone();
}
var (left, right, bits) = DoLiftBinary(binary.Left, binary.Right);
if (left != null && right != null) {
var bits = leftBits ?? rightBits;
if (rightBits != null)
bits.UnionWith(rightBits);
if (binary.HasFlag(InstructionFlags.MayThrow) && !bits.All(0, nullableVars.Count)) {
// Cannot execute potentially-throwing instruction unless all
// the nullableVars are arguments to the instruction
@ -518,9 +513,61 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -518,9 +513,61 @@ namespace ICSharpCode.Decompiler.IL.Transforms
ILRange = comp.ILRange
};
return (newInst, bits);
} else if (inst is Call call && call.Method.IsOperator) {
// Lifted user-defined operators, except for comparison operators (as those return bool, not bool?)
var liftedOperator = CSharp.Resolver.CSharpOperators.LiftUserDefinedOperator(call.Method);
if (liftedOperator == null || !NullableType.IsNullable(liftedOperator.ReturnType))
return (null, null);
ILInstruction[] newArgs;
BitSet newBits;
if (call.Arguments.Count == 1) {
var (arg, bits) = DoLift(call.Arguments[0]);
newArgs = new[] { arg };
newBits = bits;
} else if (call.Arguments.Count == 2) {
var (left, right, bits) = DoLiftBinary(call.Arguments[0], call.Arguments[1]);
newArgs = new[] { left, right };
newBits = bits;
} else {
return (null, null);
}
if (!newBits.All(0, nullableVars.Count)) {
// all nullable vars must be involved when calling a method (side effect)
return (null, null);
}
var newInst = new Call(liftedOperator) {
ConstrainedTo = call.ConstrainedTo,
IsTail = call.IsTail,
ILStackWasEmpty = call.ILStackWasEmpty,
ILRange = call.ILRange
};
newInst.Arguments.AddRange(newArgs);
return (newInst, newBits);
}
return (null, null);
}
(ILInstruction, ILInstruction, BitSet) DoLiftBinary(ILInstruction lhs, ILInstruction rhs)
{
var (left, leftBits) = DoLift(lhs);
var (right, rightBits) = DoLift(rhs);
if (left != null && right == null && SemanticHelper.IsPure(rhs.Flags)) {
// Embed non-nullable pure expression in lifted expression.
right = rhs.Clone();
}
if (left == null && right != null && SemanticHelper.IsPure(lhs.Flags)) {
// Embed non-nullable pure expression in lifted expression.
left = lhs.Clone();
}
if (left != null && right != null) {
var bits = leftBits ?? rightBits;
if (rightBits != null)
bits.UnionWith(rightBits);
return (left, right, bits);
} else {
return (null, null, null);
}
}
#endregion
#region Match...Call

Loading…
Cancel
Save