private BetterResult BetterOperator(UnaryOperatorSignature op1, UnaryOperatorSignature op2, BoundExpression operand, ref HashSet <DiagnosticInfo> useSiteDiagnostics) { // First we see if the conversion from the operand to one operand type is better than // the conversion to the other. BetterResult better = BetterConversionFromExpression(operand, op1.OperandType, op2.OperandType, ref useSiteDiagnostics); if (better == BetterResult.Left || better == BetterResult.Right) { return(better); } // There was no better member on the basis of conversions. Go to the tiebreaking round. // SPEC: In case the parameter type sequences P1, P2 and Q1, Q2 are equivalent -- that is, every Pi // SPEC: has an identity conversion to the corresponding Qi -- the following tie-breaking rules // SPEC: are applied: if (Conversions.HasIdentityConversion(op1.OperandType, op2.OperandType)) { // SPEC: If Mp has more specific parameter types than Mq then Mp is better than Mq. // Under what circumstances can two unary operators with identical signatures be "more specific" // than another? With a binary operator you could have C<T>.op+(C<T>, T) and C<T>.op+(C<T>, int). // When doing overload resolution on C<int> + int, the latter is more specific. But with a unary // operator, the sole operand *must* be the containing type or its nullable type. Therefore // if there is an identity conversion, then the parameters really were identical. We therefore // skip checking for specificity. // SPEC: If one member is a non-lifted operator and the other is a lifted operator, // SPEC: the non-lifted one is better. bool lifted1 = op1.Kind.IsLifted(); bool lifted2 = op2.Kind.IsLifted(); if (lifted1 && !lifted2) { return(BetterResult.Right); } else if (!lifted1 && lifted2) { return(BetterResult.Left); } } // Always prefer operators with val parameters over operators with in parameters: if (op1.RefKind == RefKind.None && op2.RefKind == RefKind.In) { return(BetterResult.Left); } else if (op2.RefKind == RefKind.None && op1.RefKind == RefKind.In) { return(BetterResult.Right); } return(BetterResult.Neither); }
// This method takes an array of items and a predicate which filters out the valid items. // From the valid items we find the index of the *unique best item* in the array. // In order for a valid item x to be considered best, x must be better than every other // item. The "better" relation must be consistent; that is: // // better(x,y) == Left requires that better(y,x) == Right // better(x,y) == Right requires that better(y,x) == Left // better(x,y) == Neither requires that better(y,x) == Neither // // It is possible for the array to contain the same item twice; if it does then // the duplicate is ignored. That is, having the "best" item twice does not preclude // it from being the best. // UNDONE: Update this to give a BestIndex result that indicates ambiguity. private static int?UniqueBestValidIndex <T>(ImmutableArray <T> items, Func <T, bool> valid, Func <T, T, BetterResult> better) { if (items.IsEmpty) { return(null); } int?candidateIndex = null; T candidateItem = default(T); for (int currentIndex = 0; currentIndex < items.Length; ++currentIndex) { T currentItem = items[currentIndex]; if (!valid(currentItem)) { continue; } if (candidateIndex == null) { candidateIndex = currentIndex; candidateItem = currentItem; continue; } BetterResult result = better(candidateItem, currentItem); if (result == BetterResult.Equal) { // The list had the same item twice. Just ignore it. continue; } else if (result == BetterResult.Neither) { // Neither the current item nor the candidate item are better, // and therefore neither of them can be the best. We no longer // have a candidate for best item. candidateIndex = null; candidateItem = default(T); } else if (result == BetterResult.Right) { // The candidate is worse than the current item, so replace it // with the current item. candidateIndex = currentIndex; candidateItem = currentItem; } // Otherwise, the candidate is better than the current item, so // it continues to be the candidate. } if (candidateIndex == null) { return(null); } // We had a candidate that was better than everything that came *after* it. // Now verify that it was better than everything that came before it. for (int currentIndex = 0; currentIndex < candidateIndex.Value; ++currentIndex) { T currentItem = items[currentIndex]; if (!valid(currentItem)) { continue; } BetterResult result = better(candidateItem, currentItem); if (result != BetterResult.Left && result != BetterResult.Equal) { // The candidate was not better than everything that came before it. There is // no best item. return(null); } } // The candidate was better than everything that came before it. return(candidateIndex); }
private bool VOBetterFunctionMember <TMember>( MemberResolutionResult <TMember> m1, MemberResolutionResult <TMember> m2, ArrayBuilder <BoundExpression> arguments, out BetterResult result, out HashSet <DiagnosticInfo> useSiteDiagnostics ) where TMember : Symbol { result = BetterResult.Neither; bool Ambiguous = false; // Prefer the member not declared in VulcanRT, if applicable useSiteDiagnostics = null; if (Compilation.Options.HasRuntime) { var asm1 = m1.Member.ContainingAssembly; var asm2 = m2.Member.ContainingAssembly; if (asm1 != asm2) { // prefer XSharpCore over XSharpVO, so typed versions get preference over untyped versions //if (asm1.IsXSharpCore() && asm2.IsXSharpVO()) //{ // result = BetterResult.Left; // return true; //} // prefer non runtime over runtime to allow overriding built-in functions if (asm1.IsRT() != asm2.IsRT()) { if (asm1.IsRT()) { result = BetterResult.Right; return(true); } else if (asm2.IsRT()) { result = BetterResult.Left; return(true); } } // prefer functions/method in the current assembly over external methods if (asm1.IsFromCompilation(Compilation)) { result = BetterResult.Left; return(true); } if (asm2.IsFromCompilation(Compilation)) { result = BetterResult.Right; return(true); } } if (m1.Member.HasClipperCallingConvention() != m2.Member.HasClipperCallingConvention()) { if (m1.Member.HasClipperCallingConvention()) { result = BetterResult.Right; } else { result = BetterResult.Left; } return(true); } if (m1.Member.GetParameterCount() == m2.Member.GetParameterCount()) { // In case of 2 methods with the same # of parameters // we have different / extended rules compared to C# var parsLeft = m1.Member.GetParameters(); var parsRight = m2.Member.GetParameters(); var usualType = Compilation.UsualType(); var objectType = Compilation.GetSpecialType(SpecialType.System_Object); var len = parsLeft.Length; if (arguments.Count < len) { len = arguments.Count; } bool equalLeft = true; bool equalRight = true; // check if all left types are equal if (parsLeft.Length == arguments.Count) { for (int i = 0; i < len; i++) { var parLeft = parsLeft[i]; var arg = arguments[i]; if (parLeft.Type != arg.Type) { equalLeft = false; break; } } } // check if all right types are equal if (parsRight.Length == arguments.Count) { for (int i = 0; i < len; i++) { var parRight = parsRight[i]; var arg = arguments[i]; if (parRight.Type != arg.Type) { equalRight = false; break; } } } // Only exit here when one of the two is better than the other if (equalLeft && !equalRight) { result = BetterResult.Left; return(true); } if (equalRight && !equalLeft) { result = BetterResult.Right; return(true); } for (int i = 0; i < len; i++) { var parLeft = parsLeft[i]; var parRight = parsRight[i]; var refLeft = parLeft.RefKind; var refRight = parRight.RefKind; var arg = arguments[i]; bool argCanBeByRef = arg.Kind == BoundKind.AddressOfOperator; var argType = arg.Type; if (argCanBeByRef) { var bao = arg as BoundAddressOfOperator; argType = bao.Operand.Type; } if (parLeft.Type != parRight.Type || refLeft != refRight) { // Prefer the method with a more specific parameter which is not an array type over USUAL if (parLeft.Type == usualType && argType != usualType && !parRight.Type.IsArray()) { result = BetterResult.Right; return(true); } if (parRight.Type == usualType && argType != usualType && !parLeft.Type.IsArray()) { result = BetterResult.Left; return(true); } // Prefer the method with Object type over the one with Object[] type if (parLeft.Type == objectType && parRight.Type.IsArray() && ((ArrayTypeSymbol)parRight.Type).ElementType == objectType) { result = BetterResult.Left; return(true); } if (parRight.Type == objectType && parLeft.Type.IsArray() && ((ArrayTypeSymbol)parLeft.Type).ElementType == objectType) { result = BetterResult.Right; return(true); } // Now check for REF parameters and possible REF arguments if (argCanBeByRef) { var op = arg as BoundAddressOfOperator; var opType = op?.Operand?.Type; if (refLeft == RefKind.Ref && opType == parLeft.Type) { result = BetterResult.Left; return(true); } if (refRight == RefKind.Ref && opType == parRight.Type) { result = BetterResult.Right; return(true); } if (refLeft != refRight) { if (refLeft == RefKind.Ref) { result = BetterResult.Left; return(true); } if (refRight == RefKind.Ref) { result = BetterResult.Right; return(true); } } } if (refLeft != refRight) { if (parLeft.Type == argType && refLeft != RefKind.None && argCanBeByRef) { result = BetterResult.Left; return(true); } if (parRight.Type == argType && refRight != RefKind.None && argCanBeByRef) { result = BetterResult.Right; return(true); } if (parLeft.Type == argType && refLeft == RefKind.None && !argCanBeByRef) { result = BetterResult.Left; return(true); } if (parRight.Type == argType && refRight == RefKind.None && !argCanBeByRef) { result = BetterResult.Right; return(true); } } // now fall back to original type (and not addressof type) argType = arg.Type; // Handle passing Enum values to methods that have a non enum parameter if (argType?.TypeKind == TypeKind.Enum) { // First check if they have the enum type itself if (argType == parLeft.Type) { result = BetterResult.Left; return(true); } if (argType == parRight.Type) { result = BetterResult.Right; return(true); } // Then check the underlying type argType = argType.EnumUnderlyingType(); if (argType == parLeft.Type) { result = BetterResult.Left; return(true); } if (argType == parRight.Type) { result = BetterResult.Right; return(true); } } if (argType == parLeft.Type) { result = BetterResult.Left; return(true); } if (argType == parRight.Type) { result = BetterResult.Right; return(true); } // VoFloat prefers overload with double over all other conversions if (argType == Compilation.FloatType()) { var doubleType = Compilation.GetSpecialType(SpecialType.System_Double); if (parLeft.Type == doubleType) { result = BetterResult.Left; return(true); } if (parRight.Type == doubleType) { result = BetterResult.Right; return(true); } } // handle case where argument is usual and the method is not usual // prefer method with "native VO" parameter type if (argType == Compilation.UsualType()) { // no need to check if parleft or parright are usual that was checked above if (parLeft.Type != parRight.Type) { // is there an VO style conversion possible ? var leftConvert = parLeft.Type.IsValidVOUsualType(Compilation); var rightConvert = parRight.Type.IsValidVOUsualType(Compilation); if (leftConvert != rightConvert) { // One is a valid conversion, the other is not. if (leftConvert) { result = BetterResult.Left; } else { result = BetterResult.Right; } return(true); } } } } } } // when both methods are in a functions class from different assemblies // pick the first one in the references list // if (asm1 != asm2 && string.Equals(m1.Member.ContainingType.Name, XSharpSpecialNames.FunctionsClass, XSharpString.Comparison) && string.Equals(m2.Member.ContainingType.Name, XSharpSpecialNames.FunctionsClass, XSharpString.Comparison)) { foreach (var reference in Compilation.ReferencedAssemblyNames) { if (reference.Name == asm1.Name) { result = BetterResult.Left; Ambiguous = true; } if (reference.Name == asm2.Name) { result = BetterResult.Right; Ambiguous = true; } if (Ambiguous) { TMember r1, r2; if (result == BetterResult.Left) { r1 = m1.Member; r2 = m2.Member; } else { r1 = m2.Member; r2 = m1.Member; } var info = new CSDiagnosticInfo(ErrorCode.WRN_VulcanAmbiguous, new object[] { r1.Name, r1.Kind.ToString(), new FormattedSymbol(r1, SymbolDisplayFormat.CSharpErrorMessageFormat), r2.Kind.ToString(), new FormattedSymbol(r2, SymbolDisplayFormat.CSharpErrorMessageFormat) }); useSiteDiagnostics = new HashSet <DiagnosticInfo>(); useSiteDiagnostics.Add(info); return(true); } } } } // generate warning that function takes precedence over static method var func1 = m1.Member.ContainingType.Name.EndsWith("Functions"); var func2 = m2.Member.ContainingType.Name.EndsWith("Functions"); if (func1 && !func2) { result = BetterResult.Left; var info = new CSDiagnosticInfo(ErrorCode.WRN_FunctionsTakePrecedenceOverMethods, new object[] { m1.Member.Name, new FormattedSymbol(m1.Member, SymbolDisplayFormat.CSharpErrorMessageFormat), new FormattedSymbol(m2.Member, SymbolDisplayFormat.CSharpErrorMessageFormat), m1.Member.Kind.ToString() }); useSiteDiagnostics = new HashSet <DiagnosticInfo>(); useSiteDiagnostics.Add(info); } if (func2 && !func1) { result = BetterResult.Right; var info = new CSDiagnosticInfo(ErrorCode.WRN_FunctionsTakePrecedenceOverMethods, new object[] { m2.Member.Name, new FormattedSymbol(m2.Member, SymbolDisplayFormat.CSharpErrorMessageFormat), new FormattedSymbol(m1.Member, SymbolDisplayFormat.CSharpErrorMessageFormat), m2.Member.Kind.ToString() }); useSiteDiagnostics = new HashSet <DiagnosticInfo>(); useSiteDiagnostics.Add(info); } return(false); }