internal void SetRule(MetaObjectBuilder /*!*/ metaBuilder, CallArguments /*!*/ args)
{
Assert.NotNull(metaBuilder, args);
Debug.Assert(args.SimpleArgumentCount == 0 && !args.Signature.HasBlock && !args.Signature.HasSplattedArgument && !args.Signature.HasRhsArgument);
Debug.Assert(args.Signature.HasScope);
var ec = args.RubyContext;
// implicit conversions should only depend on a static type:
if (TryImplicitConversion(metaBuilder, args))
{
if (args.Target == null)
{
metaBuilder.AddRestriction(Ast.Equal(args.TargetExpression, Ast.Constant(null, args.TargetExpression.Type)));
}
else
{
metaBuilder.AddTypeRestriction(args.Target.GetType(), args.TargetExpression);
}
return;
}
// check for type version:
metaBuilder.AddTargetTypeTest(args);
string toMethodName = ToMethodName;
Expression targetClassNameConstant = Ast.Constant(ec.GetClassOf(args.Target).Name);
// Kernel#respond_to? method is not overridden => we can optimize
RubyMemberInfo respondToMethod = ec.ResolveMethod(args.Target, Symbols.RespondTo, true).InvalidateSitesOnOverride();
if (respondToMethod == null ||
// the method is defined in library, hasn't been replaced by user defined method (TODO: maybe we should make this check better)
(respondToMethod.DeclaringModule == ec.KernelModule && respondToMethod is RubyMethodGroupInfo))
{
RubyMemberInfo conversionMethod = ec.ResolveMethod(args.Target, toMethodName, false).InvalidateSitesOnOverride();
if (conversionMethod == null)
{
// error:
SetError(metaBuilder, targetClassNameConstant, args);
return;
}
else
{
// invoke target.to_xxx() and validate it; returns an instance of TTargetType:
conversionMethod.BuildCall(metaBuilder, args, toMethodName);
if (!metaBuilder.Error && ConversionResultValidator != null)
{
metaBuilder.Result = ConversionResultValidator.OpCall(targetClassNameConstant, AstFactory.Box(metaBuilder.Result));
}
return;
}
}
else if (!RubyModule.IsMethodVisible(respondToMethod, false))
{
// respond_to? is private:
SetError(metaBuilder, targetClassNameConstant, args);
return;
}
// slow path: invoke respond_to?, to_xxx and result validation:
var conversionCallSite = Ast.Dynamic(
RubyCallAction.Make(toMethodName, RubyCallSignature.WithScope(0)),
typeof(object),
args.ScopeExpression, args.TargetExpression
);
Expression opCall;
metaBuilder.Result = Ast.Condition(
// If
// respond_to?()
Methods.IsTrue.OpCall(
Ast.Dynamic(
RubyCallAction.Make(Symbols.RespondTo, RubyCallSignature.WithScope(1)),
typeof(object),
args.ScopeExpression, args.TargetExpression, Ast.Constant(SymbolTable.StringToId(toMethodName))
)
),
// Then
// to_xxx():
opCall = (ConversionResultValidator == null) ? conversionCallSite :
ConversionResultValidator.OpCall(targetClassNameConstant, conversionCallSite),
// Else
AstUtils.Convert(
(ConversionResultValidator == null) ? args.TargetExpression :
Ast.Convert(
Ast.Throw(Methods.CreateTypeConversionError.OpCall(targetClassNameConstant, Ast.Constant(TargetTypeName))),
typeof(object)
),
opCall.Type
)
);
}