internal static Expression /*!*/ ConvertExpression(Expression /*!*/ expr, Type /*!*/ toType, RubyContext /*!*/ context, Expression /*!*/ contextExpression, bool implicitProtocolConversions) { return (ImplicitConvert(expr, expr.Type, toType) ?? ExplicitConvert(expr, expr.Type, toType) ?? AstUtils.LightDynamic(ProtocolConversionAction.GetConversionAction(context, toType, implicitProtocolConversions), toType, expr)); }
internal static void BuildConversion(MetaObjectBuilder /*!*/ metaBuilder, CallArguments /*!*/ args, Type /*!*/ resultType, params ProtocolConversionAction /*!*/[] /*!*/ conversions) { Assert.NotNull(metaBuilder, args, conversions); Debug.Assert(args.SimpleArgumentCount == 0 && !args.Signature.HasBlock && !args.Signature.HasSplattedArgument && !args.Signature.HasRhsArgument); Debug.Assert(!args.Signature.HasScope); // implicit conversions should only depend on the static type: foreach (var conversion in conversions) { if (conversion.TryImplicitConversion(metaBuilder, args)) { metaBuilder.AddObjectTypeRestriction(args.Target, args.TargetExpression); if (!metaBuilder.Error) { metaBuilder.Result = ConvertResult(metaBuilder.Result, resultType); } return; } } RubyClass targetClass = args.RubyContext.GetImmediateClassOf(args.Target); Expression targetClassNameConstant = AstUtils.Constant(targetClass.GetNonSingletonClass().Name, typeof(string)); MethodResolutionResult respondToMethod, methodMissing = MethodResolutionResult.NotFound; ProtocolConversionAction selectedConversion = null; RubyMemberInfo conversionMethod = null; using (targetClass.Context.ClassHierarchyLocker()) { // check for type version: metaBuilder.AddTargetTypeTest(args.Target, targetClass, args.TargetExpression, args.MetaContext, ArrayUtils.Insert(Symbols.RespondTo, Symbols.MethodMissing, ArrayUtils.ConvertAll(conversions, (c) => c.ToMethodName)) ); // we can optimize if Kernel#respond_to? method is not overridden: respondToMethod = targetClass.ResolveMethodForSiteNoLock(Symbols.RespondTo, VisibilityContext.AllVisible); if (respondToMethod.Found && respondToMethod.Info.DeclaringModule == targetClass.Context.KernelModule && respondToMethod.Info is RubyLibraryMethodInfo) // TODO: better override detection { respondToMethod = MethodResolutionResult.NotFound; // get the first applicable conversion: foreach (var conversion in conversions) { selectedConversion = conversion; conversionMethod = targetClass.ResolveMethodForSiteNoLock(conversion.ToMethodName, VisibilityContext.AllVisible).Info; if (conversionMethod != null) { break; } else { // find method_missing - we need to add "to_xxx" methods to the missing methods table: if (!methodMissing.Found) { methodMissing = targetClass.ResolveMethodNoLock(Symbols.MethodMissing, VisibilityContext.AllVisible); } methodMissing.InvalidateSitesOnMissingMethodAddition(conversion.ToMethodName, targetClass.Context); } } } } if (!respondToMethod.Found) { if (conversionMethod == null) { // error: selectedConversion.SetError(metaBuilder, args, targetClassNameConstant, resultType); return; } else { // invoke target.to_xxx() and validate it; returns an instance of TTargetType: conversionMethod.BuildCall(metaBuilder, args, selectedConversion.ToMethodName); if (!metaBuilder.Error) { metaBuilder.Result = ConvertResult( selectedConversion.MakeValidatorCall(args, targetClassNameConstant, metaBuilder.Result), resultType ); } return; } } // slow path: invoke respond_to?, to_xxx and result validation: for (int i = conversions.Length - 1; i >= 0; i--) { string toMethodName = conversions[i].ToMethodName; var conversionCallSite = AstUtils.LightDynamic( RubyCallAction.Make(args.RubyContext, toMethodName, RubyCallSignature.WithImplicitSelf(0)), args.TargetExpression ); metaBuilder.Result = Ast.Condition( // If // respond_to?() Methods.IsTrue.OpCall( AstUtils.LightDynamic( RubyCallAction.Make(args.RubyContext, Symbols.RespondTo, RubyCallSignature.WithImplicitSelf(1)), args.TargetExpression, Ast.Constant(args.RubyContext.CreateSymbol(toMethodName, RubyEncoding.Binary)) ) ), // Then // to_xxx(): ConvertResult( conversions[i].MakeValidatorCall(args, targetClassNameConstant, conversionCallSite), resultType ), // Else (i < conversions.Length - 1) ? metaBuilder.Result : conversions[i].MakeErrorExpression(args, targetClassNameConstant, resultType) ); } }
internal static Convertibility CanConvertFrom(DynamicMetaObject fromArg, Type /*!*/ fromType, Type /*!*/ toType, bool toNotNullable, NarrowingLevel level, bool explicitProtocolConversions, bool implicitProtocolConversions) { ContractUtils.RequiresNotNull(fromType, "fromType"); ContractUtils.RequiresNotNull(toType, "toType"); var metaConvertible = fromArg as IConvertibleMetaObject; var rubyMetaConvertible = fromArg as IConvertibleRubyMetaObject; // // narrowing level 0: // if (toType == fromType) { return(Convertibility.AlwaysConvertible); } if (fromType == typeof(DynamicNull)) { if (toNotNullable) { return(Convertibility.NotConvertible); } if (toType.IsGenericType && toType.GetGenericTypeDefinition() == typeof(Nullable <>)) { return(Convertibility.AlwaysConvertible); } if (!toType.IsValueType) { // null convertible to any reference type: return(Convertibility.AlwaysConvertible); } else if (toType == typeof(bool)) { return(Convertibility.AlwaysConvertible); } else if (!ProtocolConversionAction.HasDefaultConversion(toType)) { // null not convertible to a value type unless a protocol conversion is allowed: return(Convertibility.NotConvertible); } } // blocks: if (fromType == typeof(MissingBlockParam)) { return(new Convertibility(toType == typeof(BlockParam) && !toNotNullable, null)); } if (fromType == typeof(BlockParam) && toType == typeof(MissingBlockParam)) { return(Convertibility.AlwaysConvertible); } if (toType.IsAssignableFrom(fromType)) { return(Convertibility.AlwaysConvertible); } if (HasImplicitNumericConversion(fromType, toType)) { return(Convertibility.AlwaysConvertible); } if (CompilerHelpers.GetImplicitConverter(fromType, toType) != null) { return(Convertibility.AlwaysConvertible); } if (rubyMetaConvertible != null) { return(rubyMetaConvertible.IsConvertibleTo(toType, false)); } else if (metaConvertible != null) { return(new Convertibility(metaConvertible.CanConvertTo(toType, false), null)); } // // narrowing level 1: // if (level < NarrowingLevel.One) { return(Convertibility.NotConvertible); } if (explicitProtocolConversions && ProtocolConversionAction.HasDefaultConversion(toType)) { return(Convertibility.AlwaysConvertible); } // // narrowing level 2: // if (level < NarrowingLevel.Two) { return(Convertibility.NotConvertible); } if (HasExplicitNumericConversion(fromType, toType)) { return(Convertibility.AlwaysConvertible); } if (CompilerHelpers.GetExplicitConverter(fromType, toType) != null) { return(Convertibility.AlwaysConvertible); } if (CompilerHelpers.HasTypeConverter(fromType, toType)) { return(Convertibility.AlwaysConvertible); } if (fromType == typeof(char) && toType == typeof(string)) { return(Convertibility.AlwaysConvertible); } if (toType == typeof(bool)) { return(Convertibility.AlwaysConvertible); } if (rubyMetaConvertible != null) { return(rubyMetaConvertible.IsConvertibleTo(toType, true)); } else if (metaConvertible != null) { return(new Convertibility(metaConvertible.CanConvertTo(toType, true), null)); } // // narrowing level 3: // if (level < NarrowingLevel.Three) { return(Convertibility.NotConvertible); } if (implicitProtocolConversions && ProtocolConversionAction.HasDefaultConversion(toType)) { return(Convertibility.AlwaysConvertible); } // A COM object can potentially be converted to the given interface, but might also be not so use this only as the last resort: if (TypeUtils.IsComObjectType(fromType) && toType.IsInterface) { return(Convertibility.AlwaysConvertible); } return(Convertibility.NotConvertible); }
protected override bool Build(MetaObjectBuilder /*!*/ metaBuilder, CallArguments /*!*/ args, bool defaultFallback) { Debug.Assert(defaultFallback, "custom fallback not supported"); ProtocolConversionAction.BuildConversion(metaBuilder, args, _resultType, _conversions); return(true); }