// Return the index of the *unique* item in the array that matches the predicate, // or null if there is not one. private static BestIndex UniqueIndex <T>(ImmutableArray <T> items, Func <T, bool> predicate) { if (items.IsEmpty) { return(BestIndex.None()); } int?result = null; for (int i = 0; i < items.Length; ++i) { if (predicate(items[i])) { if (result == null) { result = i; } else { // Not unique. return(BestIndex.IsAmbiguous(result.Value, i)); } } } return(result == null?BestIndex.None() : BestIndex.HasBest(result.Value)); }
/// <summary> /// Find the most specific among a set of conversion operators, with the given constraint on the conversion. /// </summary> private static int?MostSpecificConversionOperator(Func <UserDefinedConversionAnalysis, bool> constraint, ImmutableArray <UserDefinedConversionAnalysis> u) { // SPEC: If U contains exactly one user-defined conversion operator from SX to TX // SPEC: then that is the most-specific conversion operator; // // SPEC: Otherwise, if U contains exactly one lifted conversion operator that converts from // SPEC: SX to TX then this is the most specific operator. // // SPEC: Otherwise, the conversion is ambiguous and a compile-time error occurs. // // SPEC ERROR: // // Clearly the text above cannot be correct because it gives undesirable results. // Suppose we have structs E and F with an implicit user defined conversion from // F to E. We have an assignment from F to E?. Clearly what should happen is // we should convert F to E, then convert E to E?. But the spec says that this // should be an error. Why? Because both F-->E and F?-->E? are added to the candidate // set. What is SX? Clearly F, because there is a candidate that takes an F. // What is TX? Clearly E? because there is a candidate that returns an E?. // And now the overload resolution problem is ambiguous because neither operator // takes SX and returns TX. // // DELIBERATE SPEC VIOLATION: // // The native compiler takes a rather different approach than the approach described // in the specification. Rather than adding both the lifted and unlifted forms of // each operator to the candidate set, using those operators to determine the best // source and target types, and then choosing the unique operator from that source type // to that target type, it instead *transforms in place* the "from" and "to" types // of each operator so that their nullability matches those of the source and target // types. This can then lead to ambiguities; consider for example a type that // has user defined conversions X-->Y and X-->Y?. If we have a conversion from X to // Y?, the spec would say that the operators X-->Y, its lifted form X?-->Y?, and // X-->Y? are applicable candidates and that the best of them is X-->Y?. // // The native compiler arrives at the same conclusion but by different logic; it says // that X-->Y has a "half lifted" form X-->Y?, and that it is "worse" than X-->Y? // because it is half lifted. // Therefore we match this behavior by first checking to see if there is a unique // best operator that converts from the source type to the target type with liftings // on neither side. BestIndex bestUnlifted = UniqueIndex(u, conv => constraint(conv) && LiftingCount(conv) == 0); if (bestUnlifted.Kind == BestIndexKind.Best) { return(bestUnlifted.Best); } else if (bestUnlifted.Kind == BestIndexKind.Ambiguous) { // If we got an ambiguity, don't continue. We need to bail immediately. // UNDONE: We can do better error reporting if we return the ambiguity and // use that in the error message. return(null); } // There was no fully-unlifted operator. Check to see if there was any *half-lifted* operator. // // For example, suppose we had a conversion from X-->Y?, and lifted it to X?-->Y?. (The spec // says not to do such a lifting because Y? is not a non-nullable value type, but the native // compiler does so and we are being compatible with it.) That would be a half-lifted operator. // // For example, suppose we had a conversion from X-->Y, and the assignment Y? y = new X(); -- // this would also be a "half lifted" conversion even though there is no "lifting" going on // (in the sense that we are not checking the source to see if it is null.) // BestIndex bestHalfLifted = UniqueIndex(u, conv => constraint(conv) && LiftingCount(conv) == 1); if (bestHalfLifted.Kind == BestIndexKind.Best) { return(bestHalfLifted.Best); } else if (bestHalfLifted.Kind == BestIndexKind.Ambiguous) { // UNDONE: We can do better error reporting if we return the ambiguity and // use that in the error message. return(null); } // Finally, see if there is a unique best *fully lifted* operator. BestIndex bestFullyLifted = UniqueIndex(u, conv => constraint(conv) && LiftingCount(conv) == 2); if (bestFullyLifted.Kind == BestIndexKind.Best) { return(bestFullyLifted.Best); } else if (bestFullyLifted.Kind == BestIndexKind.Ambiguous) { // UNDONE: We can do better error reporting if we return the ambiguity and // use that in the error message. return(null); } return(null); }