// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software // without restriction, including without limitation the rights to use, copy, modify, merge, // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons // to whom the Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. #nullable enable 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; namespace ICSharpCode.Decompiler.CSharp.Resolver { sealed class CSharpOperators { readonly ICompilation compilation; private CSharpOperators(ICompilation compilation) { this.compilation = compilation; InitParameterArrays(); } /// /// Gets the CSharpOperators instance for the specified . /// This will make use of the context's cache manager (if available) to reuse the CSharpOperators instance. /// public static CSharpOperators Get(ICompilation compilation) { CacheManager cache = compilation.CacheManager; CSharpOperators? operators = (CSharpOperators?)cache.GetShared(typeof(CSharpOperators)); if (operators == null) { operators = (CSharpOperators)cache.GetOrAddShared(typeof(CSharpOperators), new CSharpOperators(compilation)); } return operators; } #region class OperatorMethod OperatorMethod[] Lift(params OperatorMethod[] methods) { List result = new List(methods); foreach (OperatorMethod method in methods) { OperatorMethod? lifted = method.Lift(this); if (lifted != null) result.Add(lifted); } return result.ToArray(); } IParameter[] normalParameters = new IParameter[(int)(TypeCode.String + 1 - TypeCode.Object)]; IParameter[] nullableParameters = new IParameter[(int)(TypeCode.Decimal + 1 - TypeCode.Boolean)]; void InitParameterArrays() { for (TypeCode i = TypeCode.Object; i <= TypeCode.String; i++) { normalParameters[i - TypeCode.Object] = new DefaultParameter(compilation.FindType(i), string.Empty); } for (TypeCode i = TypeCode.Boolean; i <= TypeCode.Decimal; i++) { IType type = NullableType.Create(compilation, compilation.FindType(i)); nullableParameters[i - TypeCode.Boolean] = new DefaultParameter(type, string.Empty); } } IParameter MakeParameter(TypeCode code) { return normalParameters[code - TypeCode.Object]; } IParameter MakeNullableParameter(IParameter normalParameter) { for (TypeCode i = TypeCode.Boolean; i <= TypeCode.Decimal; i++) { if (normalParameter == normalParameters[i - TypeCode.Object]) return nullableParameters[i - TypeCode.Boolean]; } throw new ArgumentException(); } internal class OperatorMethod : IParameterizedMember { readonly ICompilation compilation; internal readonly List parameters = new List(); protected OperatorMethod(ICompilation compilation) { this.compilation = compilation; } public IReadOnlyList Parameters { get { return parameters; } } public IType ReturnType { get; internal set; } = null!; // initialized by derived class ctor public ICompilation Compilation { get { return compilation; } } public virtual OperatorMethod? Lift(CSharpOperators operators) { return null; } public System.Reflection.Metadata.EntityHandle MetadataToken => default; ITypeDefinition? IEntity.DeclaringTypeDefinition { get { return null; } } public IType DeclaringType => SpecialType.UnknownType; IMember IMember.MemberDefinition { get { return this; } } IEnumerable IMember.ExplicitlyImplementedInterfaceMembers { get { return EmptyList.Instance; } } bool IMember.IsVirtual { get { return false; } } bool IMember.IsOverride { get { return false; } } bool IMember.IsOverridable { get { return false; } } SymbolKind ISymbol.SymbolKind { get { return SymbolKind.Operator; } } IEnumerable IEntity.GetAttributes() => EmptyList.Instance; bool IEntity.HasAttribute(KnownAttribute attribute) => false; IAttribute? IEntity.GetAttribute(KnownAttribute attribute) => null; Accessibility IEntity.Accessibility { get { return Accessibility.Public; } } bool IEntity.IsStatic { get { return true; } } bool IEntity.IsAbstract { get { return false; } } bool IEntity.IsSealed { get { return false; } } bool IMember.IsExplicitInterfaceImplementation { get { return false; } } IModule IEntity.ParentModule { get { return compilation.MainModule; } } TypeParameterSubstitution IMember.Substitution { get { return TypeParameterSubstitution.Identity; } } IMember IMember.Specialize(TypeParameterSubstitution substitution) { if (TypeParameterSubstitution.Identity.Equals(substitution)) return this; throw new NotSupportedException(); } string INamedElement.FullName { get { return "operator"; } } public string Name { get { return "operator"; } } string INamedElement.Namespace { get { return string.Empty; } } string INamedElement.ReflectionName { get { return "operator"; } } public override string ToString() { StringBuilder b = new StringBuilder(); b.Append(ReturnType + " operator("); for (int i = 0; i < parameters.Count; i++) { if (i > 0) b.Append(", "); b.Append(parameters[i].Type); } b.Append(')'); return b.ToString(); } bool IMember.Equals(IMember? obj, TypeVisitor? typeNormalization) { return this == obj; } } #endregion #region Unary operator class definitions internal class UnaryOperatorMethod : OperatorMethod { public virtual bool CanEvaluateAtCompileTime { get { return false; } } public virtual object? Invoke(CSharpResolver resolver, object? input) { throw new NotSupportedException(); } public UnaryOperatorMethod(ICompilation compilation) : base(compilation) { } } sealed class LambdaUnaryOperatorMethod : UnaryOperatorMethod { readonly Func func; public LambdaUnaryOperatorMethod(CSharpOperators operators, Func func) : base(operators.compilation) { TypeCode typeCode = Type.GetTypeCode(typeof(T)); this.ReturnType = operators.compilation.FindType(typeCode); parameters.Add(operators.MakeParameter(typeCode)); this.func = func; } public override bool CanEvaluateAtCompileTime { get { return true; } } public override object? Invoke(CSharpResolver resolver, object? input) { if (input == null) return null; return func((T)resolver.CSharpPrimitiveCast(Type.GetTypeCode(typeof(T)), input)); } public override OperatorMethod Lift(CSharpOperators operators) { return new LiftedUnaryOperatorMethod(operators, this); } } sealed class LiftedUnaryOperatorMethod : UnaryOperatorMethod, ILiftedOperator { UnaryOperatorMethod baseMethod; public LiftedUnaryOperatorMethod(CSharpOperators operators, UnaryOperatorMethod baseMethod) : base(operators.compilation) { this.baseMethod = baseMethod; this.ReturnType = NullableType.Create(baseMethod.Compilation, baseMethod.ReturnType); parameters.Add(operators.MakeNullableParameter(baseMethod.Parameters[0])); } public IReadOnlyList NonLiftedParameters => baseMethod.Parameters; public IType NonLiftedReturnType => baseMethod.ReturnType; } #endregion #region Unary operator definitions // C# 4.0 spec: §7.7.1 Unary plus operator OperatorMethod[]? unaryPlusOperators; public OperatorMethod[] UnaryPlusOperators { get { OperatorMethod[]? ops = LazyInit.VolatileRead(ref unaryPlusOperators); if (ops != null) { return ops; } else { return LazyInit.GetOrSet(ref unaryPlusOperators, Lift( new LambdaUnaryOperatorMethod(this, i => +i), new LambdaUnaryOperatorMethod(this, i => +i), new LambdaUnaryOperatorMethod(this, i => +i), new LambdaUnaryOperatorMethod(this, i => +i), new LambdaUnaryOperatorMethod(this, i => +i), new LambdaUnaryOperatorMethod(this, i => +i), new LambdaUnaryOperatorMethod(this, i => +i) )); } } } // C# 4.0 spec: §7.7.2 Unary minus operator OperatorMethod[]? uncheckedUnaryMinusOperators; public OperatorMethod[] UncheckedUnaryMinusOperators { get { OperatorMethod[]? ops = LazyInit.VolatileRead(ref uncheckedUnaryMinusOperators); if (ops != null) { return ops; } else { return LazyInit.GetOrSet(ref uncheckedUnaryMinusOperators, Lift( new LambdaUnaryOperatorMethod(this, i => unchecked(-i)), new LambdaUnaryOperatorMethod(this, i => unchecked(-i)), new LambdaUnaryOperatorMethod(this, i => unchecked(-i)), new LambdaUnaryOperatorMethod(this, i => unchecked(-i)), new LambdaUnaryOperatorMethod(this, i => unchecked(-i)) )); } } } OperatorMethod[]? checkedUnaryMinusOperators; public OperatorMethod[] CheckedUnaryMinusOperators { get { OperatorMethod[]? ops = LazyInit.VolatileRead(ref checkedUnaryMinusOperators); if (ops != null) { return ops; } else { return LazyInit.GetOrSet(ref checkedUnaryMinusOperators, Lift( new LambdaUnaryOperatorMethod(this, i => checked(-i)), new LambdaUnaryOperatorMethod(this, i => checked(-i)), new LambdaUnaryOperatorMethod(this, i => checked(-i)), new LambdaUnaryOperatorMethod(this, i => checked(-i)), new LambdaUnaryOperatorMethod(this, i => checked(-i)) )); } } } // C# 4.0 spec: §7.7.3 Logical negation operator OperatorMethod[]? logicalNegationOperators; public OperatorMethod[] LogicalNegationOperators { get { OperatorMethod[]? ops = LazyInit.VolatileRead(ref logicalNegationOperators); if (ops != null) { return ops; } else { return LazyInit.GetOrSet(ref logicalNegationOperators, Lift( new LambdaUnaryOperatorMethod(this, b => !b) )); } } } // C# 4.0 spec: §7.7.4 Bitwise complement operator OperatorMethod[]? bitwiseComplementOperators; public OperatorMethod[] BitwiseComplementOperators { get { OperatorMethod[]? ops = LazyInit.VolatileRead(ref bitwiseComplementOperators); if (ops != null) { return ops; } else { return LazyInit.GetOrSet(ref bitwiseComplementOperators, Lift( new LambdaUnaryOperatorMethod(this, i => ~i), new LambdaUnaryOperatorMethod(this, i => ~i), new LambdaUnaryOperatorMethod(this, i => ~i), new LambdaUnaryOperatorMethod(this, i => ~i) )); } } } #endregion #region Binary operator class definitions internal class BinaryOperatorMethod : OperatorMethod { public virtual bool CanEvaluateAtCompileTime { get { return false; } } public virtual object? Invoke(CSharpResolver resolver, object? lhs, object? rhs) { throw new NotSupportedException(); } public BinaryOperatorMethod(ICompilation compilation) : base(compilation) { } } sealed class LambdaBinaryOperatorMethod : BinaryOperatorMethod { readonly Func checkedFunc; readonly Func uncheckedFunc; public LambdaBinaryOperatorMethod(CSharpOperators operators, Func func) : this(operators, func, func) { } public LambdaBinaryOperatorMethod(CSharpOperators operators, Func checkedFunc, Func uncheckedFunc) : base(operators.compilation) { TypeCode t1 = Type.GetTypeCode(typeof(T1)); this.ReturnType = operators.compilation.FindType(t1); parameters.Add(operators.MakeParameter(t1)); parameters.Add(operators.MakeParameter(Type.GetTypeCode(typeof(T2)))); this.checkedFunc = checkedFunc; this.uncheckedFunc = uncheckedFunc; } public override bool CanEvaluateAtCompileTime { get { return true; } } public override object? Invoke(CSharpResolver resolver, object? lhs, object? rhs) { if (lhs == null || rhs == null) return null; Func func = resolver.CheckForOverflow ? checkedFunc : uncheckedFunc; return func((T1)resolver.CSharpPrimitiveCast(Type.GetTypeCode(typeof(T1)), lhs), (T2)resolver.CSharpPrimitiveCast(Type.GetTypeCode(typeof(T2)), rhs)); } public override OperatorMethod Lift(CSharpOperators operators) { return new LiftedBinaryOperatorMethod(operators, this); } } sealed class LiftedBinaryOperatorMethod : BinaryOperatorMethod, ILiftedOperator { readonly BinaryOperatorMethod baseMethod; public LiftedBinaryOperatorMethod(CSharpOperators operators, BinaryOperatorMethod baseMethod) : base(operators.compilation) { this.baseMethod = baseMethod; this.ReturnType = NullableType.Create(operators.compilation, baseMethod.ReturnType); parameters.Add(operators.MakeNullableParameter(baseMethod.Parameters[0])); parameters.Add(operators.MakeNullableParameter(baseMethod.Parameters[1])); } public IReadOnlyList NonLiftedParameters => baseMethod.Parameters; public IType NonLiftedReturnType => baseMethod.ReturnType; } #endregion #region Arithmetic operators // C# 4.0 spec: §7.8.1 Multiplication operator OperatorMethod[]? multiplicationOperators; public OperatorMethod[] MultiplicationOperators { get { OperatorMethod[]? ops = LazyInit.VolatileRead(ref multiplicationOperators); if (ops != null) { return ops; } else { return LazyInit.GetOrSet(ref multiplicationOperators, Lift( new LambdaBinaryOperatorMethod(this, (a, b) => checked(a * b), (a, b) => unchecked(a * b)), new LambdaBinaryOperatorMethod(this, (a, b) => checked(a * b), (a, b) => unchecked(a * b)), new LambdaBinaryOperatorMethod(this, (a, b) => checked(a * b), (a, b) => unchecked(a * b)), new LambdaBinaryOperatorMethod(this, (a, b) => checked(a * b), (a, b) => unchecked(a * b)), new LambdaBinaryOperatorMethod(this, (a, b) => checked(a * b), (a, b) => unchecked(a * b)), new LambdaBinaryOperatorMethod(this, (a, b) => checked(a * b), (a, b) => unchecked(a * b)), new LambdaBinaryOperatorMethod(this, (a, b) => checked(a * b), (a, b) => unchecked(a * b)) )); } } } // C# 4.0 spec: §7.8.2 Division operator OperatorMethod[]? divisionOperators; public OperatorMethod[] DivisionOperators { get { OperatorMethod[]? ops = LazyInit.VolatileRead(ref divisionOperators); if (ops != null) { return ops; } else { return LazyInit.GetOrSet(ref divisionOperators, Lift( new LambdaBinaryOperatorMethod(this, (a, b) => checked(a / b), (a, b) => unchecked(a / b)), new LambdaBinaryOperatorMethod(this, (a, b) => checked(a / b), (a, b) => unchecked(a / b)), new LambdaBinaryOperatorMethod(this, (a, b) => checked(a / b), (a, b) => unchecked(a / b)), new LambdaBinaryOperatorMethod(this, (a, b) => checked(a / b), (a, b) => unchecked(a / b)), new LambdaBinaryOperatorMethod(this, (a, b) => checked(a / b), (a, b) => unchecked(a / b)), new LambdaBinaryOperatorMethod(this, (a, b) => checked(a / b), (a, b) => unchecked(a / b)), new LambdaBinaryOperatorMethod(this, (a, b) => checked(a / b), (a, b) => unchecked(a / b)) )); } } } // C# 4.0 spec: §7.8.3 Remainder operator OperatorMethod[]? remainderOperators; public OperatorMethod[] RemainderOperators { get { OperatorMethod[]? ops = LazyInit.VolatileRead(ref remainderOperators); if (ops != null) { return ops; } else { return LazyInit.GetOrSet(ref remainderOperators, Lift( new LambdaBinaryOperatorMethod(this, (a, b) => checked(a % b), (a, b) => unchecked(a % b)), new LambdaBinaryOperatorMethod(this, (a, b) => checked(a % b), (a, b) => unchecked(a % b)), new LambdaBinaryOperatorMethod(this, (a, b) => checked(a % b), (a, b) => unchecked(a % b)), new LambdaBinaryOperatorMethod(this, (a, b) => checked(a % b), (a, b) => unchecked(a % b)), new LambdaBinaryOperatorMethod(this, (a, b) => checked(a % b), (a, b) => unchecked(a % b)), new LambdaBinaryOperatorMethod(this, (a, b) => checked(a % b), (a, b) => unchecked(a % b)), new LambdaBinaryOperatorMethod(this, (a, b) => checked(a % b), (a, b) => unchecked(a % b)) )); } } } // C# 4.0 spec: §7.8.3 Addition operator OperatorMethod[]? additionOperators; public OperatorMethod[] AdditionOperators { get { OperatorMethod[]? ops = LazyInit.VolatileRead(ref additionOperators); if (ops != null) { return ops; } else { return LazyInit.GetOrSet(ref additionOperators, Lift( new LambdaBinaryOperatorMethod(this, (a, b) => checked(a + b), (a, b) => unchecked(a + b)), new LambdaBinaryOperatorMethod(this, (a, b) => checked(a + b), (a, b) => unchecked(a + b)), new LambdaBinaryOperatorMethod(this, (a, b) => checked(a + b), (a, b) => unchecked(a + b)), new LambdaBinaryOperatorMethod(this, (a, b) => checked(a + b), (a, b) => unchecked(a + b)), new LambdaBinaryOperatorMethod(this, (a, b) => checked(a + b), (a, b) => unchecked(a + b)), new LambdaBinaryOperatorMethod(this, (a, b) => checked(a + b), (a, b) => unchecked(a + b)), new LambdaBinaryOperatorMethod(this, (a, b) => checked(a + b), (a, b) => unchecked(a + b)), new StringConcatenation(this, TypeCode.String, TypeCode.String), new StringConcatenation(this, TypeCode.String, TypeCode.Object), new StringConcatenation(this, TypeCode.Object, TypeCode.String) )); } } } // not in this list, but handled manually: enum addition, delegate combination sealed class StringConcatenation : BinaryOperatorMethod { bool canEvaluateAtCompileTime; public StringConcatenation(CSharpOperators operators, TypeCode p1, TypeCode p2) : base(operators.compilation) { this.canEvaluateAtCompileTime = p1 == TypeCode.String && p2 == TypeCode.String; this.ReturnType = operators.compilation.FindType(KnownTypeCode.String); parameters.Add(operators.MakeParameter(p1)); parameters.Add(operators.MakeParameter(p2)); } public override bool CanEvaluateAtCompileTime { get { return canEvaluateAtCompileTime; } } public override object? Invoke(CSharpResolver? resolver, object? lhs, object? rhs) { return string.Concat(lhs, rhs); } } // C# 4.0 spec: §7.8.4 Subtraction operator OperatorMethod[]? subtractionOperators; public OperatorMethod[] SubtractionOperators { get { OperatorMethod[]? ops = LazyInit.VolatileRead(ref subtractionOperators); if (ops != null) { return ops; } else { return LazyInit.GetOrSet(ref subtractionOperators, Lift( new LambdaBinaryOperatorMethod(this, (a, b) => checked(a - b), (a, b) => unchecked(a - b)), new LambdaBinaryOperatorMethod(this, (a, b) => checked(a - b), (a, b) => unchecked(a - b)), new LambdaBinaryOperatorMethod(this, (a, b) => checked(a - b), (a, b) => unchecked(a - b)), new LambdaBinaryOperatorMethod(this, (a, b) => checked(a - b), (a, b) => unchecked(a - b)), new LambdaBinaryOperatorMethod(this, (a, b) => checked(a - b), (a, b) => unchecked(a - b)), new LambdaBinaryOperatorMethod(this, (a, b) => checked(a - b), (a, b) => unchecked(a - b)), new LambdaBinaryOperatorMethod(this, (a, b) => checked(a - b), (a, b) => unchecked(a - b)) )); } } } // C# 4.0 spec: §7.8.5 Shift operators OperatorMethod[]? shiftLeftOperators; public OperatorMethod[] ShiftLeftOperators { get { OperatorMethod[]? ops = LazyInit.VolatileRead(ref shiftLeftOperators); if (ops != null) { return ops; } else { return LazyInit.GetOrSet(ref shiftLeftOperators, Lift( new LambdaBinaryOperatorMethod(this, (a, b) => a << b), new LambdaBinaryOperatorMethod(this, (a, b) => a << b), new LambdaBinaryOperatorMethod(this, (a, b) => a << b), new LambdaBinaryOperatorMethod(this, (a, b) => a << b) )); } } } OperatorMethod[]? shiftRightOperators; public OperatorMethod[] ShiftRightOperators { get { OperatorMethod[]? ops = LazyInit.VolatileRead(ref shiftRightOperators); if (ops != null) { return ops; } else { return LazyInit.GetOrSet(ref shiftRightOperators, Lift( new LambdaBinaryOperatorMethod(this, (a, b) => a >> b), new LambdaBinaryOperatorMethod(this, (a, b) => a >> b), new LambdaBinaryOperatorMethod(this, (a, b) => a >> b), new LambdaBinaryOperatorMethod(this, (a, b) => a >> b) )); } } } #endregion #region Equality operators sealed class EqualityOperatorMethod : BinaryOperatorMethod { public readonly TypeCode Type; public readonly bool Negate; public EqualityOperatorMethod(CSharpOperators operators, TypeCode type, bool negate) : base(operators.compilation) { this.Negate = negate; this.Type = type; this.ReturnType = operators.compilation.FindType(KnownTypeCode.Boolean); parameters.Add(operators.MakeParameter(type)); parameters.Add(operators.MakeParameter(type)); } public override bool CanEvaluateAtCompileTime { get { return Type != TypeCode.Object; } } public override object Invoke(CSharpResolver resolver, object? lhs, object? rhs) { if (lhs == null && rhs == null) return !Negate; // ==: true; !=: false if (lhs == null || rhs == null) return Negate; // ==: false; !=: true lhs = resolver.CSharpPrimitiveCast(Type, lhs); rhs = resolver.CSharpPrimitiveCast(Type, rhs); bool equal; if (Type == TypeCode.Single) { equal = (float)lhs == (float)rhs; } else if (Type == TypeCode.Double) { equal = (double)lhs == (double)rhs; } else { equal = object.Equals(lhs, rhs); } return equal ^ Negate; } public override OperatorMethod? Lift(CSharpOperators operators) { if (Type == TypeCode.Object || Type == TypeCode.String) return null; else return new LiftedEqualityOperatorMethod(operators, this); } } sealed class LiftedEqualityOperatorMethod : BinaryOperatorMethod, ILiftedOperator { readonly EqualityOperatorMethod baseMethod; public LiftedEqualityOperatorMethod(CSharpOperators operators, EqualityOperatorMethod baseMethod) : base(operators.compilation) { this.baseMethod = baseMethod; this.ReturnType = baseMethod.ReturnType; IParameter p = operators.MakeNullableParameter(baseMethod.Parameters[0]); parameters.Add(p); parameters.Add(p); } public override bool CanEvaluateAtCompileTime { get { return baseMethod.CanEvaluateAtCompileTime; } } public override object Invoke(CSharpResolver resolver, object? lhs, object? rhs) { return baseMethod.Invoke(resolver, lhs, rhs); } public IReadOnlyList NonLiftedParameters => baseMethod.Parameters; public IType NonLiftedReturnType => baseMethod.ReturnType; } // C# 4.0 spec: §7.10 Relational and type-testing operators static readonly TypeCode[] valueEqualityOperatorsFor = { TypeCode.Int32, TypeCode.UInt32, TypeCode.Int64, TypeCode.UInt64, TypeCode.Single, TypeCode.Double, TypeCode.Decimal, TypeCode.Boolean }; OperatorMethod[]? valueEqualityOperators; public OperatorMethod[] ValueEqualityOperators { get { OperatorMethod[]? ops = LazyInit.VolatileRead(ref valueEqualityOperators); if (ops != null) { return ops; } else { return LazyInit.GetOrSet(ref valueEqualityOperators, Lift( valueEqualityOperatorsFor.Select(c => new EqualityOperatorMethod(this, c, false)).ToArray() )); } } } OperatorMethod[]? valueInequalityOperators; public OperatorMethod[] ValueInequalityOperators { get { OperatorMethod[]? ops = LazyInit.VolatileRead(ref valueInequalityOperators); if (ops != null) { return ops; } else { return LazyInit.GetOrSet(ref valueInequalityOperators, Lift( valueEqualityOperatorsFor.Select(c => new EqualityOperatorMethod(this, c, true)).ToArray() )); } } } OperatorMethod[]? referenceEqualityOperators; public OperatorMethod[] ReferenceEqualityOperators { get { OperatorMethod[]? ops = LazyInit.VolatileRead(ref referenceEqualityOperators); if (ops != null) { return ops; } else { return LazyInit.GetOrSet(ref referenceEqualityOperators, Lift( new EqualityOperatorMethod(this, TypeCode.Object, false), new EqualityOperatorMethod(this, TypeCode.String, false) )); } } } OperatorMethod[]? referenceInequalityOperators; public OperatorMethod[] ReferenceInequalityOperators { get { OperatorMethod[]? ops = LazyInit.VolatileRead(ref referenceInequalityOperators); if (ops != null) { return ops; } else { return LazyInit.GetOrSet(ref referenceInequalityOperators, Lift( new EqualityOperatorMethod(this, TypeCode.Object, true), new EqualityOperatorMethod(this, TypeCode.String, true) )); } } } #endregion #region Relational Operators sealed class RelationalOperatorMethod : BinaryOperatorMethod { readonly Func func; public RelationalOperatorMethod(CSharpOperators operators, Func func) : base(operators.compilation) { this.ReturnType = operators.compilation.FindType(KnownTypeCode.Boolean); parameters.Add(operators.MakeParameter(Type.GetTypeCode(typeof(T1)))); parameters.Add(operators.MakeParameter(Type.GetTypeCode(typeof(T2)))); this.func = func; } public override bool CanEvaluateAtCompileTime { get { return true; } } public override object? Invoke(CSharpResolver resolver, object? lhs, object? rhs) { if (lhs == null || rhs == null) return null; return func((T1)resolver.CSharpPrimitiveCast(Type.GetTypeCode(typeof(T1)), lhs), (T2)resolver.CSharpPrimitiveCast(Type.GetTypeCode(typeof(T2)), rhs)); } public override OperatorMethod Lift(CSharpOperators operators) { var lifted = new LiftedBinaryOperatorMethod(operators, this); lifted.ReturnType = this.ReturnType; // don't lift the return type for relational operators return lifted; } } OperatorMethod[]? lessThanOperators; public OperatorMethod[] LessThanOperators { get { OperatorMethod[]? ops = LazyInit.VolatileRead(ref lessThanOperators); if (ops != null) { return ops; } else { return LazyInit.GetOrSet(ref lessThanOperators, Lift( new RelationalOperatorMethod(this, (a, b) => a < b), new RelationalOperatorMethod(this, (a, b) => a < b), new RelationalOperatorMethod(this, (a, b) => a < b), new RelationalOperatorMethod(this, (a, b) => a < b), new RelationalOperatorMethod(this, (a, b) => a < b), new RelationalOperatorMethod(this, (a, b) => a < b), new RelationalOperatorMethod(this, (a, b) => a < b) )); } } } OperatorMethod[]? lessThanOrEqualOperators; public OperatorMethod[] LessThanOrEqualOperators { get { OperatorMethod[]? ops = LazyInit.VolatileRead(ref lessThanOrEqualOperators); if (ops != null) { return ops; } else { return LazyInit.GetOrSet(ref lessThanOrEqualOperators, Lift( new RelationalOperatorMethod(this, (a, b) => a <= b), new RelationalOperatorMethod(this, (a, b) => a <= b), new RelationalOperatorMethod(this, (a, b) => a <= b), new RelationalOperatorMethod(this, (a, b) => a <= b), new RelationalOperatorMethod(this, (a, b) => a <= b), new RelationalOperatorMethod(this, (a, b) => a <= b), new RelationalOperatorMethod(this, (a, b) => a <= b) )); } } } OperatorMethod[]? greaterThanOperators; public OperatorMethod[] GreaterThanOperators { get { OperatorMethod[]? ops = LazyInit.VolatileRead(ref greaterThanOperators); if (ops != null) { return ops; } else { return LazyInit.GetOrSet(ref greaterThanOperators, Lift( new RelationalOperatorMethod(this, (a, b) => a > b), new RelationalOperatorMethod(this, (a, b) => a > b), new RelationalOperatorMethod(this, (a, b) => a > b), new RelationalOperatorMethod(this, (a, b) => a > b), new RelationalOperatorMethod(this, (a, b) => a > b), new RelationalOperatorMethod(this, (a, b) => a > b), new RelationalOperatorMethod(this, (a, b) => a > b) )); } } } OperatorMethod[]? greaterThanOrEqualOperators; public OperatorMethod[] GreaterThanOrEqualOperators { get { OperatorMethod[]? ops = LazyInit.VolatileRead(ref greaterThanOrEqualOperators); if (ops != null) { return ops; } else { return LazyInit.GetOrSet(ref greaterThanOrEqualOperators, Lift( new RelationalOperatorMethod(this, (a, b) => a >= b), new RelationalOperatorMethod(this, (a, b) => a >= b), new RelationalOperatorMethod(this, (a, b) => a >= b), new RelationalOperatorMethod(this, (a, b) => a >= b), new RelationalOperatorMethod(this, (a, b) => a >= b), new RelationalOperatorMethod(this, (a, b) => a >= b), new RelationalOperatorMethod(this, (a, b) => a >= b) )); } } } #endregion #region Bitwise operators OperatorMethod[]? logicalAndOperators; public OperatorMethod[] LogicalAndOperators { get { OperatorMethod[]? ops = LazyInit.VolatileRead(ref logicalAndOperators); if (ops != null) { return ops; } else { return LazyInit.GetOrSet(ref logicalAndOperators, new OperatorMethod[] { new LambdaBinaryOperatorMethod(this, (a, b) => a & b) }); } } } OperatorMethod[]? bitwiseAndOperators; public OperatorMethod[] BitwiseAndOperators { get { OperatorMethod[]? ops = LazyInit.VolatileRead(ref bitwiseAndOperators); if (ops != null) { return ops; } else { return LazyInit.GetOrSet(ref bitwiseAndOperators, Lift( new LambdaBinaryOperatorMethod(this, (a, b) => a & b), new LambdaBinaryOperatorMethod(this, (a, b) => a & b), new LambdaBinaryOperatorMethod(this, (a, b) => a & b), new LambdaBinaryOperatorMethod(this, (a, b) => a & b), this.LogicalAndOperators[0] )); } } } OperatorMethod[]? logicalOrOperators; public OperatorMethod[] LogicalOrOperators { get { OperatorMethod[]? ops = LazyInit.VolatileRead(ref logicalOrOperators); if (ops != null) { return ops; } else { return LazyInit.GetOrSet(ref logicalOrOperators, new OperatorMethod[] { new LambdaBinaryOperatorMethod(this, (a, b) => a | b) }); } } } OperatorMethod[]? bitwiseOrOperators; public OperatorMethod[] BitwiseOrOperators { get { OperatorMethod[]? ops = LazyInit.VolatileRead(ref bitwiseOrOperators); if (ops != null) { return ops; } else { return LazyInit.GetOrSet(ref bitwiseOrOperators, Lift( new LambdaBinaryOperatorMethod(this, (a, b) => a | b), new LambdaBinaryOperatorMethod(this, (a, b) => a | b), new LambdaBinaryOperatorMethod(this, (a, b) => a | b), new LambdaBinaryOperatorMethod(this, (a, b) => a | b), this.LogicalOrOperators[0] )); } } } // Note: the logic for the lifted bool? bitwise operators is wrong; // we produce "true | null" = "null" when it should be true. However, this is irrelevant // because bool? cannot be a compile-time type. OperatorMethod[]? bitwiseXorOperators; public OperatorMethod[] BitwiseXorOperators { get { OperatorMethod[]? ops = LazyInit.VolatileRead(ref bitwiseXorOperators); if (ops != null) { return ops; } else { return LazyInit.GetOrSet(ref bitwiseXorOperators, Lift( new LambdaBinaryOperatorMethod(this, (a, b) => a ^ b), new LambdaBinaryOperatorMethod(this, (a, b) => a ^ b), new LambdaBinaryOperatorMethod(this, (a, b) => a ^ b), new LambdaBinaryOperatorMethod(this, (a, b) => a ^ b), new LambdaBinaryOperatorMethod(this, (a, b) => a ^ b) )); } } } #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 compilation = nonLiftedMethod.Compilation; var substitution = nonLiftedMethod.Substitution; this.Parameters = base.CreateParameters( type => NullableType.Create(compilation, type.AcceptVisitor(substitution))); // Comparison operators keep the 'bool' return type even when lifted. if (IsComparisonOperator(nonLiftedMethod)) this.ReturnType = nonLiftedMethod.ReturnType; else this.ReturnType = NullableType.Create(compilation, nonLiftedMethod.ReturnType.AcceptVisitor(substitution)); } public IReadOnlyList 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; } } #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; } IReadOnlyList NonLiftedParameters { get; } } }