public AddTargetTypeTest ( object target, |
||
target | object | |
targetClass | ||
targetParameter | Expression | |
metaContext | ||
resolvedNames | IEnumerable |
|
return | void |
public void AddTargetTypeTest(object target, RubyClass/*!*/ targetClass, Expression/*!*/ targetParameter, DynamicMetaObject/*!*/ metaContext,
IEnumerable<string>/*!*/ resolvedNames) {
// no changes to the module's class hierarchy while building the test:
targetClass.Context.RequiresClassHierarchyLock();
// initialization changes the version number, so ensure that the module is initialized:
targetClass.InitializeMethodsNoLock();
var context = (RubyContext)metaContext.Value;
if (target is IRubyObject) {
Type type = target.GetType();
AddTypeRestriction(type, targetParameter);
// Ruby objects (get the method directly to prevent interface dispatch):
MethodInfo classGetter = type.GetMethod(Methods.IRubyObject_get_ImmediateClass.Name, BindingFlags.Public | BindingFlags.Instance);
if (type.IsVisible && classGetter != null && classGetter.ReturnType == typeof(RubyClass)) {
AddCondition(
// (#{type})target.ImmediateClass.Version.Method == #{immediateClass.Version.Method}
Ast.Equal(
Ast.Field(
Ast.Field(
Ast.Call(Ast.Convert(targetParameter, type), classGetter),
Fields.RubyModule_Version
),
Fields.VersionHandle_Method
),
AstUtils.Constant(targetClass.Version.Method)
)
);
return;
}
// TODO: explicit iface-implementation
throw new NotSupportedException("Type implementing IRubyObject should be visible and have ImmediateClass getter");
}
AddRuntimeTest(metaContext);
// singleton nil:
if (target == null) {
AddRestriction(Ast.Equal(targetParameter, AstUtils.Constant(null)));
AddVersionTest(context.NilClass);
return;
}
// singletons true, false:
if (target is bool) {
AddRestriction(Ast.AndAlso(
Ast.TypeIs(targetParameter, typeof(bool)),
Ast.Equal(Ast.Convert(targetParameter, typeof(bool)), AstUtils.Constant(target))
));
AddVersionTest((bool)target ? context.TrueClass : context.FalseClass);
return;
}
var nominalClass = targetClass.NominalClass;
Debug.Assert(!nominalClass.IsSingletonClass);
Debug.Assert(!nominalClass.IsRubyClass);
// Do we need a singleton check?
if (nominalClass.ClrSingletonMethods == null ||
CollectionUtils.TrueForAll(resolvedNames, (methodName) => !nominalClass.ClrSingletonMethods.ContainsKey(methodName))) {
// no: there is no singleton subclass of target class that defines any method being called:
AddTypeRestriction(target.GetType(), targetParameter);
AddVersionTest(targetClass);
} else if (targetClass.IsSingletonClass) {
// yes: check whether the incoming object is a singleton and the singleton has the right version:
AddTypeRestriction(target.GetType(), targetParameter);
AddCondition(Methods.IsClrSingletonRuleValid.OpCall(
metaContext.Expression,
targetParameter,
AstUtils.Constant(targetClass.Version.Method)
));
} else {
// yes: check whether the incoming object is NOT a singleton and the class has the right version:
AddTypeRestriction(target.GetType(), targetParameter);
AddCondition(Methods.IsClrNonSingletonRuleValid.OpCall(
metaContext.Expression,
targetParameter,
Ast.Constant(targetClass.Version),
AstUtils.Constant(targetClass.Version.Method)
));
}
}
public static DynamicMetaObject TryBind(RubyContext/*!*/ context, GetMemberBinder/*!*/ binder, DynamicMetaObject/*!*/ target) { Assert.NotNull(context, target); var metaBuilder = new MetaObjectBuilder(); var contextExpression = AstUtils.Constant(context); RubyClass targetClass = context.GetImmediateClassOf(target.Value); MethodResolutionResult method; RubyMemberInfo methodMissing = null; using (targetClass.Context.ClassHierarchyLocker()) { metaBuilder.AddTargetTypeTest(target.Value, targetClass, target.Expression, context, contextExpression); method = targetClass.ResolveMethodForSiteNoLock(binder.Name, RubyClass.IgnoreVisibility); if (method.Found) { methodMissing = targetClass.ResolveMethodForSiteNoLock(Symbols.MethodMissing, RubyClass.IgnoreVisibility).Info; } } if (method.Found) { // we need to create a bound member: metaBuilder.Result = AstUtils.Constant(new RubyMethod(target.Value, method.Info, binder.Name)); } else { // TODO: // We need to throw an exception if we don't find method_missing so that our version update optimization works: // This limits interop with other languages. // // class B CLR type with method 'foo' // class C < B Ruby class // x = C.new // // 1. x.GET("foo") from Ruby // No method found or CLR method found -> fallback to Python // Python might see its method foo or might just fallback to .NET, // in any case it will add rule [1] with restriction on type of C w/o Ruby version check. // 2. B.define_method("foo") // This doesn't update C due to the optimization (there is no overridden method foo in C). // 3. x.GET("foo") from Ruby // This will not invoke the binder since the rule [1] is still valid. // object symbol = SymbolTable.StringToId(binder.Name); RubyCallAction.BindToMethodMissing(metaBuilder, new CallArguments( new DynamicMetaObject(contextExpression, BindingRestrictions.Empty, context), new[] { target, new DynamicMetaObject(AstUtils.Constant(symbol), BindingRestrictions.Empty, symbol) }, RubyCallSignature.Simple(1) ), binder.Name, methodMissing, method.IncompatibleVisibility, false ); } // TODO: we should return null if we fail, we need to throw exception for now: return metaBuilder.CreateMetaObject(binder, DynamicMetaObject.EmptyMetaObjects); }