internal static bool BuildConversion(MetaObjectBuilder /*!*/ metaBuilder, DynamicMetaObject /*!*/ target, Expression /*!*/ contextExpression, Type /*!*/ toType, bool defaultFallback) { Expression expr = TryImplicitConversion(target, toType); if (expr != null) { metaBuilder.Result = expr; metaBuilder.AddObjectTypeRestriction(target.Value, target.Expression); return(true); } if (defaultFallback) { metaBuilder.AddObjectTypeRestriction(target.Value, target.Expression); metaBuilder.SetError(Methods.MakeTypeConversionError.OpCall( contextExpression, AstUtils.Convert(target.Expression, typeof(object)), Ast.Constant(toType, typeof(Type)) )); return(true); } return(false); }
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) ); } }