diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpOperators.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpOperators.cs index 33e9955e5..35d935882 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpOperators.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpOperators.cs @@ -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 } } - sealed class LiftedUnaryOperatorMethod : UnaryOperatorMethod, OverloadResolution.ILiftedOperator + sealed class LiftedUnaryOperatorMethod : UnaryOperatorMethod, ILiftedOperator { UnaryOperatorMethod baseMethod; @@ -328,10 +329,9 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver this.ReturnType = NullableType.Create(baseMethod.Compilation, baseMethod.ReturnType); this.Parameters.Add(operators.MakeNullableParameter(baseMethod.Parameters[0])); } - - public IList NonLiftedParameters { - get { return baseMethod.Parameters; } - } + + public IList NonLiftedParameters => baseMethod.Parameters; + public IType NonLiftedReturnType => baseMethod.ReturnType; } #endregion @@ -484,7 +484,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver } } - sealed class LiftedBinaryOperatorMethod : BinaryOperatorMethod, OverloadResolution.ILiftedOperator + sealed class LiftedBinaryOperatorMethod : BinaryOperatorMethod, ILiftedOperator { readonly BinaryOperatorMethod baseMethod; @@ -496,10 +496,9 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver this.Parameters.Add(operators.MakeNullableParameter(baseMethod.Parameters[0])); this.Parameters.Add(operators.MakeNullableParameter(baseMethod.Parameters[1])); } - - public IList NonLiftedParameters { - get { return baseMethod.Parameters; } - } + + public IList NonLiftedParameters => baseMethod.Parameters; + public IType NonLiftedReturnType => baseMethod.ReturnType; } #endregion @@ -728,7 +727,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver } } - sealed class LiftedEqualityOperatorMethod : BinaryOperatorMethod, OverloadResolution.ILiftedOperator + sealed class LiftedEqualityOperatorMethod : BinaryOperatorMethod, ILiftedOperator { readonly EqualityOperatorMethod baseMethod; @@ -750,10 +749,9 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver { return baseMethod.Invoke(resolver, lhs, rhs); } - - public IList NonLiftedParameters { - get { return baseMethod.Parameters; } - } + + public IList 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 } } #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 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 + } + + /// + /// Implement this interface to give overload resolution a hint that the member represents a lifted operator, + /// which is used in the tie-breaking rules. + /// + public interface ILiftedOperator : IParameterizedMember + { + IType NonLiftedReturnType { get; } + IList NonLiftedParameters { get; } } } diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs index 2d12dedd4..d438828a3 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs @@ -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 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 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 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 diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolution.cs b/ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolution.cs index 444ce2d45..67393df51 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolution.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolution.cs @@ -684,15 +684,6 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver return 0; } - /// - /// Implement this interface to give overload resolution a hint that the member represents a lifted operator, - /// which is used in the tie-breaking rules. - /// - public interface ILiftedOperator : IParameterizedMember - { - IList NonLiftedParameters { get; } - } - int MoreSpecificFormalParameters(Candidate c1, Candidate c2) { // prefer the member with more formal parmeters (in case both have different number of optional parameters) diff --git a/ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs index a6281e80e..f846cb754 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs @@ -103,4 +103,23 @@ namespace ICSharpCode.Decompiler.IL && Patterns.ListMatch.DoMatch(this.Arguments, o.Arguments, ref match); } } + + partial class Call : ILiftableInstruction + { + /// + /// 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. + /// + 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(); + } + } + } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs index 93812a5b2..1fbf2c94d 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs @@ -317,22 +317,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// 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 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 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 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