// // User-defined conversions // public static Expression UserDefinedConversion (ResolveContext rc, Expression source, TypeSpec target, UserConversionRestriction restr, Location loc) { List<MethodSpec> candidates = null; // // If S or T are nullable types, source_type and target_type are their underlying types // otherwise source_type and target_type are equal to S and T respectively. // TypeSpec source_type = source.Type; TypeSpec target_type = target; Expression source_type_expr; bool nullable_source = false; var implicitOnly = (restr & UserConversionRestriction.ImplicitOnly) != 0; if (source_type.IsNullableType) { // No unwrapping conversion S? -> T for non-reference types if (implicitOnly && !TypeSpec.IsReferenceType (target_type) && !target_type.IsNullableType) { source_type_expr = source; } else { source_type_expr = Nullable.Unwrap.CreateUnwrapped (source); source_type = source_type_expr.Type; nullable_source = true; } } else { source_type_expr = source; } if (target_type.IsNullableType) target_type = Nullable.NullableInfo.GetUnderlyingType (target_type); // Only these containers can contain a user defined implicit or explicit operators const MemberKind user_conversion_kinds = MemberKind.Class | MemberKind.Struct | MemberKind.TypeParameter; if ((source_type.Kind & user_conversion_kinds) != 0 && source_type.BuiltinType != BuiltinTypeSpec.Type.Decimal) { bool declared_only = source_type.IsStruct; var operators = MemberCache.GetUserOperator (source_type, Operator.OpType.Implicit, declared_only); if (operators != null) { FindApplicableUserDefinedConversionOperators (rc, operators, source_type_expr, target_type, restr, ref candidates); } if (!implicitOnly) { operators = MemberCache.GetUserOperator (source_type, Operator.OpType.Explicit, declared_only); if (operators != null) { FindApplicableUserDefinedConversionOperators (rc, operators, source_type_expr, target_type, restr, ref candidates); } } } if ((target.Kind & user_conversion_kinds) != 0 && target_type.BuiltinType != BuiltinTypeSpec.Type.Decimal) { bool declared_only = target.IsStruct || implicitOnly; var operators = MemberCache.GetUserOperator (target_type, Operator.OpType.Implicit, declared_only); if (operators != null) { FindApplicableUserDefinedConversionOperators (rc, operators, source_type_expr, target_type, restr, ref candidates); } if (!implicitOnly) { operators = MemberCache.GetUserOperator (target_type, Operator.OpType.Explicit, declared_only); if (operators != null) { FindApplicableUserDefinedConversionOperators (rc, operators, source_type_expr, target_type, restr, ref candidates); } } } if (candidates == null) return null; // // Find the most specific conversion operator // MethodSpec most_specific_operator; TypeSpec s_x, t_x; if (candidates.Count == 1) { most_specific_operator = candidates[0]; s_x = most_specific_operator.Parameters.Types[0]; t_x = most_specific_operator.ReturnType; } else { // // Pass original source type to find the best match against input type and // not the unwrapped expression // s_x = FindMostSpecificSource (rc, candidates, source.Type, source_type_expr, !implicitOnly); if (s_x == null) return null; t_x = FindMostSpecificTarget (candidates, target, !implicitOnly); if (t_x == null) return null; most_specific_operator = null; for (int i = 0; i < candidates.Count; ++i) { if (candidates[i].ReturnType == t_x && candidates[i].Parameters.Types[0] == s_x) { most_specific_operator = candidates[i]; break; } } if (most_specific_operator == null) { // // Unless running in probing more // if ((restr & UserConversionRestriction.ProbingOnly) == 0) { MethodSpec ambig_arg = candidates [0]; most_specific_operator = candidates [1]; /* foreach (var candidate in candidates) { if (candidate.ReturnType == t_x) most_specific_operator = candidate; else if (candidate.Parameters.Types[0] == s_x) ambig_arg = candidate; } */ rc.Report.Error (457, loc, "Ambiguous user defined operators `{0}' and `{1}' when converting from `{2}' to `{3}'", ambig_arg.GetSignatureForError (), most_specific_operator.GetSignatureForError (), source.Type.GetSignatureForError (), target.GetSignatureForError ()); } return ErrorExpression.Instance; } } // // Convert input type when it's different to best operator argument // if (s_x != source_type) { var c = source as Constant; if (c != null) { source = c.Reduce (rc, s_x); if (source == null) c = null; } if (c == null) { source = implicitOnly ? ImplicitConversionStandard (rc, source_type_expr, s_x, loc) : ExplicitConversionStandard (rc, source_type_expr, s_x, loc); } } else { source = source_type_expr; } source = new UserCast (most_specific_operator, source, loc).Resolve (rc); // // Convert result type when it's different to best operator return type // if (t_x != target_type) { // // User operator is of T? // if (t_x.IsNullableType && (target.IsNullableType || !implicitOnly)) { // // User operator return type does not match target type we need // yet another conversion. This should happen for promoted numeric // types only // if (t_x != target) { var unwrap = Nullable.Unwrap.CreateUnwrapped (source); source = implicitOnly ? ImplicitConversionStandard (rc, unwrap, target_type, loc) : ExplicitConversionStandard (rc, unwrap, target_type, loc); if (source == null) return null; if (target.IsNullableType) source = new Nullable.LiftedConversion (source, unwrap, target).Resolve (rc); } } else { source = implicitOnly ? ImplicitConversionStandard (rc, source, target_type, loc) : ExplicitConversionStandard (rc, source, target_type, loc); if (source == null) return null; } } // // Source expression is of nullable type and underlying conversion returns // only non-nullable type we need to lift it manually // if (nullable_source && !s_x.IsNullableType) return new Nullable.LiftedConversion (source, source_type_expr, target).Resolve (rc); // // Target is of nullable type but source type is not, wrap the result expression // if (target.IsNullableType && !t_x.IsNullableType) source = Nullable.Wrap.Create (source, target); return source; }
static void FindApplicableUserDefinedConversionOperators (ResolveContext rc, IList<MemberSpec> operators, Expression source, TypeSpec target, UserConversionRestriction restr, ref List<MethodSpec> candidates) { if (source.Type.IsInterface) { // Neither A nor B are interface-types return; } // For a conversion operator to be applicable, it must be possible // to perform a standard conversion from the source type to // the operand type of the operator, and it must be possible // to perform a standard conversion from the result type of // the operator to the target type. Expression texpr = null; foreach (MethodSpec op in operators) { // Can be null because MemberCache.GetUserOperator does not resize the array if (op == null) continue; var t = op.Parameters.Types[0]; if (source.Type != t && !ImplicitStandardConversionExists (rc, source, t)) { if ((restr & UserConversionRestriction.ImplicitOnly) != 0) continue; if (!ImplicitStandardConversionExists (new EmptyExpression (t), source.Type)) continue; } if ((restr & UserConversionRestriction.NullableSourceOnly) != 0 && !t.IsNullableType) continue; t = op.ReturnType; if (t.IsInterface) continue; if (target != t) { if (t.IsNullableType) t = Nullable.NullableInfo.GetUnderlyingType (t); if (!ImplicitStandardConversionExists (new EmptyExpression (t), target)) { if ((restr & UserConversionRestriction.ImplicitOnly) != 0) continue; if (texpr == null) texpr = new EmptyExpression (target); if (!ImplicitStandardConversionExists (texpr, t)) continue; } } if (candidates == null) candidates = new List<MethodSpec> (); candidates.Add (op); } }