public MethodResolutionResult(RubyMemberInfo/*!*/ info, RubyModule/*!*/ owner, bool visible) { Assert.NotNull(info, owner); _info = info; _owner = owner; _visible = visible; }
internal UnboundMethod(RubyModule/*!*/ targetConstraint, string/*!*/ name, RubyMemberInfo/*!*/ info) { Assert.NotNull(targetConstraint, name, info); _name = name; _info = info; _targetConstraint = targetConstraint; }
private static MethodMissingBinding BindToKernelMethodMissing(MetaObjectBuilder /*!*/ metaBuilder, CallArguments /*!*/ args, string /*!*/ methodName, RubyMemberInfo methodMissing, RubyMethodVisibility incompatibleVisibility, bool isSuperCall) { // TODO: better specialization of method_missing methods if (methodMissing == null || methodMissing.DeclaringModule == methodMissing.Context.KernelModule && methodMissing is RubyLibraryMethodInfo) { if (isSuperCall) { metaBuilder.SetError(Methods.MakeMissingSuperException.OpCall(AstUtils.Constant(methodName))); } else if (incompatibleVisibility == RubyMethodVisibility.Private) { metaBuilder.SetError(Methods.MakePrivateMethodCalledError.OpCall( AstUtils.Convert(args.MetaContext.Expression, typeof(RubyContext)), args.TargetExpression, AstUtils.Constant(methodName)) ); } else if (incompatibleVisibility == RubyMethodVisibility.Protected) { metaBuilder.SetError(Methods.MakeProtectedMethodCalledError.OpCall( AstUtils.Convert(args.MetaContext.Expression, typeof(RubyContext)), args.TargetExpression, AstUtils.Constant(methodName)) ); } else { return(MethodMissingBinding.Fallback); } return(MethodMissingBinding.Error); } return(MethodMissingBinding.Custom); }
// Returns true if the call was bound (with success or failure), false if fallback should be performed. internal static bool BuildMethodMissingAccess(MetaObjectBuilder /*!*/ metaBuilder, CallArguments /*!*/ args, string /*!*/ methodName, RubyMemberInfo methodMissing, RubyMethodVisibility incompatibleVisibility, bool isSuperCall, bool defaultFallback) { switch (BindToKernelMethodMissing(metaBuilder, args, methodName, methodMissing, incompatibleVisibility, isSuperCall)) { case MethodMissingBinding.Custom: // we pretend we found the member and return a method that calls method_missing: Debug.Assert(!metaBuilder.Error); metaBuilder.Result = Methods.CreateBoundMissingMember.OpCall( AstUtils.Convert(args.TargetExpression, typeof(object)), Ast.Constant(methodMissing, typeof(RubyMemberInfo)), Ast.Constant(methodName) ); return(true); case MethodMissingBinding.Error: // method_missing is defined in Kernel, error has been reported: return(true); case MethodMissingBinding.Fallback: // method_missing is defined in Kernel: if (defaultFallback) { metaBuilder.SetError(Methods.MakeMissingMemberError.OpCall(Ast.Constant(methodName))); return(true); } return(false); } throw Assert.Unreachable; }
internal static RubyMemberInfo/*!*/ SelectOverload(RubyContext/*!*/ context, RubyMemberInfo/*!*/ info, string/*!*/ name, object[]/*!*/ typeArgs) { RubyMemberInfo result = info.TrySelectOverload(Protocols.ToTypes(context, typeArgs)); if (result == null) { throw RubyExceptions.CreateArgumentError("no overload of `{0}' matches given parameter types", name); } return result; }
public RubyRepresenter(RubyContext/*!*/ context, Serializer/*!*/ serializer, YamlOptions/*!*/ opts) : base(serializer, opts) { _context = context; _objectToYamlMethod = context.GetClass(typeof(object)).ResolveMethod("to_yaml", VisibilityContext.AllVisible).Info; _TagUri = CallSite<Func<CallSite, object, object>>.Create( RubyCallAction.Make(context, "taguri", RubyCallSignature.WithImplicitSelf(0)) ); _ToYamlStyle = CallSite<Func<CallSite, object, object>>.Create( RubyCallAction.Make(context, "to_yaml_style", RubyCallSignature.WithImplicitSelf(0)) ); _ToYamlNode = CallSite<Func<CallSite, object, RubyRepresenter, object>>.Create( RubyCallAction.Make(context, "to_yaml_node", RubyCallSignature.WithImplicitSelf(1)) ); _ToYaml = CallSite<Func<CallSite, object, RubyRepresenter, object>>.Create( RubyCallAction.Make(context, "to_yaml", RubyCallSignature.WithImplicitSelf(0)) ); _ToYamlProperties = CallSite<Func<CallSite, object, object>>.Create( RubyCallAction.Make(context, "to_yaml_properties", RubyCallSignature.WithImplicitSelf(0)) ); }
internal static RubyMemberInfo/*!*/ BindGenericParameters(RubyContext/*!*/ context, RubyMemberInfo/*!*/ info, string/*!*/ name, object[]/*!*/ typeArgs) { RubyMemberInfo result = info.TryBindGenericParameters(Protocols.ToTypes(context, typeArgs)); if (result == null) { throw RubyExceptions.CreateArgumentError("wrong number of generic arguments for `{0}'", name); } return result; }
public MethodResolutionResult(RubyMemberInfo /*!*/ info, RubyModule /*!*/ owner, bool visible) { Assert.NotNull(info, owner); _info = info; _owner = owner; _visible = visible; }
public RubyMethod(object target, RubyMemberInfo/*!*/ info, string/*!*/ name) { ContractUtils.RequiresNotNull(info, "info"); ContractUtils.RequiresNotNull(name, "name"); _target = target; _info = info; _name = name; }
public static MetaObject TryBind(RubyContext /*!*/ context, GetMemberBinder /*!*/ binder, MetaObject /*!*/ target) { Assert.NotNull(context, target); var metaBuilder = new MetaObjectBuilder(); var contextExpression = Ast.Constant(context); metaBuilder.AddTargetTypeTest(target.Value, target.Expression, context, contextExpression); RubyMemberInfo method = context.ResolveMethod(target.Value, binder.Name, true).InvalidateSitesOnOverride(); if (method != null && RubyModule.IsMethodVisible(method, false)) { // we need to create a bound member: metaBuilder.Result = Ast.Constant(new RubyMethod(target.Value, method, 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, binder.Name, new CallArguments( new MetaObject(contextExpression, Restrictions.Empty, context), new[] { target, new MetaObject(Ast.Constant(symbol), Restrictions.Empty, symbol) }, RubyCallSignature.Simple(1) ), method != null ); } // TODO: we should return null if we fail, we need to throw exception for now: return(metaBuilder.CreateMetaObject(binder, MetaObject.EmptyMetaObjects)); }
internal static MethodResolutionResult Resolve(MetaObjectBuilder /*!*/ metaBuilder, string /*!*/ methodName, CallArguments /*!*/ args, out RubyMemberInfo methodMissing) { MethodResolutionResult method; var targetClass = args.TargetClass; var visibilityContext = GetVisibilityContext(args.Signature, args.Scope); using (targetClass.Context.ClassHierarchyLocker()) { metaBuilder.AddTargetTypeTest(args.Target, targetClass, args.TargetExpression, args.MetaContext, new[] { methodName, Symbols.MethodMissing } ); if (args.Signature.IsSuperCall) { Debug.Assert(!args.Signature.IsVirtualCall && args.Signature.HasImplicitSelf); method = targetClass.ResolveSuperMethodNoLock(methodName, targetClass).InvalidateSitesOnOverride(); } else { var options = args.Signature.IsVirtualCall ? MethodLookup.Virtual : MethodLookup.Default; method = targetClass.ResolveMethodForSiteNoLock(methodName, visibilityContext, options); } if (!method.Found) { methodMissing = targetClass.ResolveMethodMissingForSite(methodName, method.IncompatibleVisibility); } else { methodMissing = null; } } // Whenever the current self's class changes we need to invalidate the rule, if a protected method is being called. if (method.Info != null && method.Info.IsProtected && visibilityContext.Class != null) { // We don't need to compare versions, just the class objects (super-class relationship cannot be changed). // Since we don't want to hold on a class object (to make it collectible) we compare references to the version handlers. metaBuilder.AddCondition(Ast.Equal( Methods.GetSelfClassVersionHandle.OpCall(AstUtils.Convert(args.MetaScope.Expression, typeof(RubyScope))), Ast.Constant(visibilityContext.Class.Version) )); } return(method); }
internal void SetRule(MetaObjectBuilder /*!*/ metaBuilder, CallArguments /*!*/ args) { RubyModule currentDeclaringModule; string currentMethodName; var scope = args.Scope; object target; scope.GetSuperCallTarget(out currentDeclaringModule, out currentMethodName, out target); var targetExpression = metaBuilder.GetTemporary(typeof(object), "#super-self"); metaBuilder.AddCondition( Methods.IsSuperCallTarget.OpCall( AstUtils.Convert(args.ScopeExpression, typeof(RubyScope)), Ast.Constant(currentDeclaringModule), AstUtils.Constant(currentMethodName), targetExpression ) ); args.SetTarget(targetExpression, target); Debug.Assert(currentDeclaringModule != null); // target is stored in a local, therefore it cannot be part of the restrictions: metaBuilder.TreatRestrictionsAsConditions = true; metaBuilder.AddTargetTypeTest(target, targetExpression, scope.RubyContext, args.ContextExpression); metaBuilder.TreatRestrictionsAsConditions = false; RubyMemberInfo method = scope.RubyContext.ResolveSuperMethod(target, currentMethodName, currentDeclaringModule); // super calls don't go to method_missing if (method == null) { metaBuilder.SetError(Methods.MakeMissingSuperException.OpCall(Ast.Constant(currentMethodName))); } else { method.InvalidateSitesOnOverride = true; method.BuildSuperCall(metaBuilder, args, currentMethodName, currentDeclaringModule); } }
/// <exception cref="MissingMethodException">The resolved method is Kernel#method_missing.</exception> internal static void Bind(MetaObjectBuilder /*!*/ metaBuilder, string /*!*/ methodName, CallArguments /*!*/ args) { metaBuilder.AddTargetTypeTest(args); RubyMemberInfo method = args.RubyContext.ResolveMethod(args.Target, methodName, true).InvalidateSitesOnOverride(); if (method != null && RubyModule.IsMethodVisible(method, args.Signature.HasImplicitSelf)) { method.BuildCall(metaBuilder, args, methodName); } else { // insert the method name argument into the args object symbol = SymbolTable.StringToId(methodName); args.InsertSimple(0, new MetaObject(Ast.Constant(symbol), Restrictions.Empty, symbol)); BindToMethodMissing(metaBuilder, methodName, args, method != null); } }
private bool TryGetClrField(Type/*!*/ type, BindingFlags bindingFlags, bool inherited, bool isWrite, string/*!*/ name, out RubyMemberInfo method) { FieldInfo fieldInfo; if (inherited) { fieldInfo = type.GetInheritedFields(name).WithBindingFlags(bindingFlags).FirstOrDefault(); } else { fieldInfo = type.GetDeclaredField(name).WithBindingFlags(bindingFlags); } if (fieldInfo != null && IsVisible(fieldInfo) && (!isWrite || IsWriteable(fieldInfo))) { // creates detached info if only declared members are requested (used by Kernel#clr_member): method = new RubyFieldInfo(fieldInfo, RubyMemberFlags.Public, this, isWrite, isDetached: inherited); return true; } method = null; return false; }
private static void SetClassMethodsVisibility(RubyContext/*!*/ context, RubyModule/*!*/ module, string[]/*!*/ methodNames, RubyMethodVisibility visibility) { var methods = new RubyMemberInfo[methodNames.Length]; for (int i = 0; i < methods.Length; i++) { RubyMemberInfo method = module.SingletonClass.ResolveMethod(methodNames[i], true); if (method == null) { throw RubyExceptions.CreateUndefinedMethodError(module, methodNames[i]); } methods[i] = method; } for (int i = 0; i < methods.Length; i++) { module.SingletonClass.SetMethodVisibility(context, methodNames[i], methods[i], visibility); } }
/// <summary> /// There are basically 4 cases: /// 1) CLR method of the given name is not defined in the specified type. /// Do nothing, the method will be found as we traverse the hierarhy towards the Kernel module. /// 2) Otherwise /// 1) There is no RubyMemberInfo of given <c>name</c> present in the (type..Kernel] ancestors. /// We need to search all types in (type..Object] for CLR method overloads. /// 2) There is a RubyMemberInfo in a class, say C, in (type..Kernel]. /// We need to get CLR methods from (type..C) in addition to the members in the type. /// 1) C.HidesInheritedOverloads == true /// All overloads of the method we look for are in [type..C). /// 2) C.HidesInheritedOverloads == false /// All overloads of the method we look for are in [type..C) and in the RubyMemberInfo. /// </summary> internal bool TryGetClrMethod(RubyClass /*!*/ cls, Type /*!*/ type, BindingFlags bindingFlags, string /*!*/ name, string /*!*/ clrName, out RubyMemberInfo method) { // declared only: MemberInfo[] initialMembers = GetDeclaredClrMethods(type, bindingFlags, clrName); int initialVisibleMemberCount = GetVisibleMethodCount(initialMembers); if (initialVisibleMemberCount == 0) { // case [1] method = null; return(false); } // inherited overloads: List <RubyClass> ancestors = new List <RubyClass>(); RubyMemberInfo inheritedRubyMember = null; bool skipHidden = false; cls.ForEachAncestor((module) => { if (module != cls) { if (module.TryGetDefinedMethod(name, ref skipHidden, out inheritedRubyMember) && !inheritedRubyMember.IsSuperForwarder) { return(true); } // Skip classes that have no tracker, e.g. Fixnum(tracker) <: Integer(null) <: Numeric(null) <: Object(tracker). // Skip interfaces, their methods are not callable => do not include them into a method group. // Skip all classes once hidden sentinel is encountered (no CLR overloads are visible since then). if (!skipHidden && module.TypeTracker != null && module.IsClass) { ancestors.Add((RubyClass)module); } } // continue: return(false); }); _allMethods = null; if (inheritedRubyMember != null) { // case [2.2.2]: add CLR methods from the Ruby member: var inheritedGroup = inheritedRubyMember as RubyMethodGroupInfo; if (inheritedGroup != null) { AddMethodsOverwriteExisting(inheritedGroup.MethodBases, inheritedGroup.OverloadOwners); } } // populate classes in (type..Kernel] or (type..C) with method groups: for (int i = ancestors.Count - 1; i >= 0; i--) { var declared = GetDeclaredClrMethods(ancestors[i].TypeTracker.Type, bindingFlags, clrName); if (declared.Length != 0 && AddMethodsOverwriteExisting(declared, null)) { // There is no cached method that needs to be invalidated. // // Proof: // Suppose the group being created here overridden an existing method that is cached in a dynamic site invoked on some target class. // Then either the target class is above all ancestors[i] or below some. If it is above then the new group doesn't // invalidate validity of the site. If it is below then the method resolution for the cached method would create // and store to method tables all method groups in between the target class and the owner of the cached method, including the // one that contain overloads of ancestors[i]. But no module below inheritedRubyMember contains a method group of the name // being currently resolved. ancestors[i].AddMethodNoCacheInvalidation(name, MakeAllMethodsGroup(ancestors[i])); } } if (_allMethods != null) { // add members declared in self: AddMethodsOverwriteExisting(initialMembers, null); // return the group, it will be stored in the method table by the caller: method = MakeAllMethodsGroup(cls); } else { method = MakeSingleOwnerGroup(cls, initialMembers, initialVisibleMemberCount); } return(true); }
// thread safe: doesn't need any lock since it only accesses immutable state public bool TryGetClrConstructor(out RubyMemberInfo method) { OverloadInfo[] ctors; if (TypeTracker != null && (ctors = GetConstructors(TypeTracker.Type).ToArray()).Length > 0) { method = new RubyMethodGroupInfo(ctors, this, true); return true; } method = null; return false; }
/// <summary> /// Returns a fresh instance of RubyMemberInfo each time it is called. The caller needs to cache it if appropriate. /// May add or use method groups to/from super-clases if BindingFlags.DeclaredOnly is used. /// </summary> private bool TryGetClrMember(Type/*!*/ type, string/*!*/ name, bool mapNames, bool unmangleNames, bool inherited, out RubyMemberInfo method) { Context.RequiresClassHierarchyLock(); BindingFlags basicBindingFlags = BindingFlags.Public | BindingFlags.NonPublic; // We look only for members directly declared on the type and handle method overloads inheritance manually. BindingFlags bindingFlags = basicBindingFlags | ((_isSingletonClass) ? BindingFlags.Static : BindingFlags.Instance); // instance methods on Object are also available in static context: if (type == typeof(Object)) { bindingFlags |= BindingFlags.Instance; } string operatorName; if (mapNames && !_isSingletonClass && (operatorName = RubyUtils.ToClrOperatorName(name)) != null) { // instance invocation of an operator: if (TryGetClrMethod(type, basicBindingFlags | BindingFlags.Static, inherited, true, name, null, operatorName, null, out method)) { return true; } } else if (mapNames && (name == "[]" || name == "[]=")) { if (type.IsArray && !_isSingletonClass) { bool isSetter = name.Length == 3; TryGetClrMethod(type, bindingFlags, inherited, false, name, null, isSetter ? "Set" : "Get", null, out method); Debug.Assert(method != null); return true; } else { var defaultMember = type.GetTypeInfo().GetCustomAttributes<DefaultMemberAttribute>(false).SingleOrDefault(); if (defaultMember != null) { // default indexer accessor: bool isSetter = name.Length == 3; if (TryGetClrProperty(type, bindingFlags, inherited, isSetter, name, defaultMember.MemberName, null, out method)) { return true; } } } } else if (name.LastCharacter() == '=') { string propertyName = name.Substring(0, name.Length - 1); string altName = unmangleNames ? RubyUtils.TryUnmangleMethodName(propertyName) : null; // property setter: if (TryGetClrProperty(type, bindingFlags, inherited, true, name, propertyName, altName, out method)) return true; // writeable field: if (TryGetClrField(type, bindingFlags, inherited, true, propertyName, altName, out method)) return true; } else { string altName = unmangleNames ? RubyUtils.TryUnmangleMethodName(name) : null; // method: if (TryGetClrMethod(type, bindingFlags, inherited, false, name, null, name, altName, out method)) return true; // getter: if (TryGetClrProperty(type, bindingFlags, inherited, false, name, name, altName, out method)) return true; // event: if (TryGetClrEvent(type, bindingFlags, inherited, name, altName, out method)) return true; // field: if (TryGetClrField(type, bindingFlags, inherited, false, name, altName, out method)) return true; } method = null; return false; }
private static void BuildOverriddenInitializerCall(MetaObjectBuilder/*!*/ metaBuilder, CallArguments/*!*/ args, RubyMemberInfo/*!*/ initializer) { var instanceExpr = metaBuilder.Result; metaBuilder.Result = null; var instanceVariable = metaBuilder.GetTemporary(instanceExpr.Type, "#instance"); // We know an exact type of the new instance and that there is no singleton for that instance. // We also have the exact method we need to call ("initialize" is a RubyMethodInfo/RubyLambdaMethodInfo). // => no tests are necessary: args.SetTarget(instanceVariable, null); if (initializer is RubyMethodInfo || initializer is RubyLambdaMethodInfo) { initializer.BuildCallNoFlow(metaBuilder, args, Symbols.Initialize); } else { // TODO: we need more refactoring of RubyMethodGroupInfo.BuildCall to be able to inline this: metaBuilder.Result = AstUtils.LightDynamic( RubyCallAction.Make(args.RubyContext, "initialize", new RubyCallSignature( args.Signature.ArgumentCount, (args.Signature.Flags & ~RubyCallFlags.IsInteropCall) | RubyCallFlags.HasImplicitSelf ) ), args.GetCallSiteArguments(instanceVariable) ); } if (!metaBuilder.Error) { // PropagateRetrySingleton(instance = new <type>(), instance.initialize(<args>)) metaBuilder.Result = Methods.PropagateRetrySingleton.OpCall( Ast.Assign(instanceVariable, instanceExpr), metaBuilder.Result ); // we need to handle break, which unwinds to a proc-converter that could be this method's frame: if (args.Signature.HasBlock) { metaBuilder.ControlFlowBuilder = RubyMethodInfo.RuleControlFlowBuilder; } } }
internal void PrepareMethodUpdate(string/*!*/ methodName, RubyMemberInfo/*!*/ method, int mixinsToSkip) { Context.RequiresClassHierarchyLock(); bool superClassUpdated = false; // A singleton subclass of a CLR type that doesn't implement IRubyObject: if (_isSingletonClass && !_superClass.IsSingletonClass && !_superClass.IsRubyClass && !(_singletonClassOf is IRubyObject)) { var ssm = _superClass.ClrSingletonMethods; if (ssm != null) { if (!ssm.ContainsKey(methodName)) { ssm[methodName] = true; _superClass.MethodsUpdated("SetSingletonMethod: " + methodName); superClassUpdated = true; } } else { _superClass.ClrSingletonMethods = ssm = new Dictionary<string, bool>(); ssm[methodName] = true; _superClass.MethodsUpdated("SetSingletonMethod: " + methodName); superClassUpdated = true; } } // Method table doesn't need to be initialized here. No site could be bound to this class or its subclass // without initializing this class. So there would be nothing to invalidate. if (MethodInitializationNeeded) { return; } Debug.Assert(mixinsToSkip <= Mixins.Length); int modulesToSkip, updatedLevel; if (mixinsToSkip > 0) { // skip this class when looking for overridden method: modulesToSkip = mixinsToSkip + 1; // update groups in this class as well: updatedLevel = _level - 1; } else { modulesToSkip = 0; updatedLevel = _level; } RubyMemberInfo overriddenMethod = ResolveOverriddenMethod(methodName, modulesToSkip); if (overriddenMethod == null) { // super class and this class have already been updated, no need to update again: if (!superClassUpdated) { var missingMethods = Context.MissingMethodsCachedInSites; if (missingMethods != null && missingMethods.Contains(methodName)) { MethodsUpdated("SetMethod: " + methodName); } } } else { if (overriddenMethod.InvalidateSitesOnOverride && !superClassUpdated) { MethodsUpdated("SetMethod: " + methodName); } // If the overridden method is not a group the groups below don't need to be updated since they don't include any overloads // from above the current method definition. RubyOverloadGroupInfo overriddenGroup = overriddenMethod as RubyOverloadGroupInfo; if (overriddenGroup != null) { // It suffice to compare the level of the overridden group. // Reason: If there was any overload visible to a group below updatedLevel it would be visible to the overriddenGroup as well. if (overriddenGroup.MaxCachedOverloadLevel > updatedLevel) { // Search the subtree of this class and remove all method groups that cache an overload owned by a super class. if (mixinsToSkip > 0) { InvalidateGroupsInSubClasses(methodName, overriddenGroup.MaxCachedOverloadLevel); } else { InvalidateGroupsInDependentClasses(methodName, overriddenGroup.MaxCachedOverloadLevel); } if (method.IsRemovable) { method.InvalidateGroupsOnRemoval = true; } } } else { // if the overridden method requires group invalidation on removal then the overridding method requires it too: if (method.IsRemovable && overriddenMethod.InvalidateGroupsOnRemoval) { method.InvalidateGroupsOnRemoval = true; } } } }
internal static void SetInvalidateSitesOnOverride(RubyMemberInfo /*!*/ member) { member._invalidateSitesOnOverride = true; }
private bool TryGetClrField(Type/*!*/ type, BindingFlags bindingFlags, bool isWrite, string/*!*/ name, out RubyMemberInfo method) { FieldInfo fieldInfo = type.GetField(name, bindingFlags); if (fieldInfo != null && IsVisible(fieldInfo) && (!isWrite || IsWriteable(fieldInfo))) { // creates detached info if only declared members are requested (used by Kernel#clr_member): bool createDetached = (bindingFlags & BindingFlags.DeclaredOnly) != 0; method = new RubyFieldInfo(fieldInfo, RubyMemberFlags.Public, this, isWrite, createDetached); return true; } method = null; return false; }
internal Curried(object target, RubyMemberInfo/*!*/ info, string/*!*/ methodNameArg) : base(target, info, "method_missing") { _methodNameArg = methodNameArg; }
protected override bool Build(MetaObjectBuilder /*!*/ metaBuilder, CallArguments /*!*/ args, bool defaultFallback) { RubyModule currentDeclaringModule; string currentMethodName; var scope = args.Scope; var scopeExpr = AstUtils.Convert(args.MetaScope.Expression, typeof(RubyScope)); RubyScope targetScope; int scopeNesting = scope.GetSuperCallTarget(out currentDeclaringModule, out currentMethodName, out targetScope); if (scopeNesting == -1) { metaBuilder.AddCondition(Methods.IsSuperOutOfMethodScope.OpCall(scopeExpr)); metaBuilder.SetError(Methods.MakeTopLevelSuperException.OpCall()); return(true); } object target = targetScope.SelfObject; var targetExpression = metaBuilder.GetTemporary(typeof(object), "#super-self"); var assignTarget = Ast.Assign( targetExpression, Methods.GetSuperCallTarget.OpCall(scopeExpr, AstUtils.Constant(scopeNesting)) ); if (_signature.HasImplicitArguments && targetScope.Kind == ScopeKind.BlockMethod) { metaBuilder.AddCondition(Ast.NotEqual(assignTarget, Ast.Field(null, Fields.NeedsUpdate))); metaBuilder.SetError(Methods.MakeImplicitSuperInBlockMethodError.OpCall()); return(true); } // If we need to update we return RubyOps.NeedsUpdate instance that will cause the subsequent conditions to fail: metaBuilder.AddInitialization(assignTarget); args.SetTarget(targetExpression, target); Debug.Assert(currentDeclaringModule != null); RubyMemberInfo method; RubyMemberInfo methodMissing = null; // MRI bug: Uses currentDeclaringModule for method look-up so we can end up with an instance method of class C // called on a target of another class. See http://redmine.ruby-lang.org/issues/show/2419. // we need to lock the hierarchy of the target class: var targetClass = scope.RubyContext.GetImmediateClassOf(target); using (targetClass.Context.ClassHierarchyLocker()) { // initialize all methods in ancestors: targetClass.InitializeMethodsNoLock(); // target is stored in a local, therefore it cannot be part of the restrictions: metaBuilder.TreatRestrictionsAsConditions = true; metaBuilder.AddTargetTypeTest(target, targetClass, targetExpression, args.MetaContext, new[] { Symbols.MethodMissing } // currentMethodName is resolved for super, which cannot be an instance singleton ); metaBuilder.TreatRestrictionsAsConditions = false; method = targetClass.ResolveSuperMethodNoLock(currentMethodName, currentDeclaringModule).InvalidateSitesOnOverride().Info; if (_signature.ResolveOnly) { metaBuilder.Result = AstUtils.Constant(method != null); return(true); } if (method == null) { // MRI: method_missing is called for the targetClass, not for the super: methodMissing = targetClass.ResolveMethodMissingForSite(currentMethodName, RubyMethodVisibility.None); } } if (method != null) { method.BuildSuperCall(metaBuilder, args, currentMethodName, currentDeclaringModule); } else { return(RubyCallAction.BuildMethodMissingCall(metaBuilder, args, currentMethodName, methodMissing, RubyMethodVisibility.None, true, defaultFallback)); } return(true); }
private static MethodMissingBinding BindToKernelMethodMissing(MetaObjectBuilder/*!*/ metaBuilder, CallArguments/*!*/ args, string/*!*/ methodName, RubyMemberInfo methodMissing, RubyMethodVisibility incompatibleVisibility, bool isSuperCall) { // TODO: better specialization of method_missing methods if (methodMissing == null || methodMissing.DeclaringModule == methodMissing.Context.KernelModule && methodMissing is RubyLibraryMethodInfo) { if (isSuperCall) { metaBuilder.SetError(Methods.MakeMissingSuperException.OpCall(AstUtils.Constant(methodName))); } else if (incompatibleVisibility == RubyMethodVisibility.Private) { metaBuilder.SetError(Methods.MakePrivateMethodCalledError.OpCall( AstUtils.Convert(args.MetaContext.Expression, typeof(RubyContext)), args.TargetExpression, AstUtils.Constant(methodName)) ); } else if (incompatibleVisibility == RubyMethodVisibility.Protected) { metaBuilder.SetError(Methods.MakeProtectedMethodCalledError.OpCall( AstUtils.Convert(args.MetaContext.Expression, typeof(RubyContext)), args.TargetExpression, AstUtils.Constant(methodName)) ); } else { return MethodMissingBinding.Fallback; } return MethodMissingBinding.Error; } return MethodMissingBinding.Custom; }
// Returns true if the call was bound (with success or failure), false if fallback should be performed. internal static bool BuildMethodMissingAccess(MetaObjectBuilder/*!*/ metaBuilder, CallArguments/*!*/ args, string/*!*/ methodName, RubyMemberInfo methodMissing, RubyMethodVisibility incompatibleVisibility, bool isSuperCall, bool defaultFallback) { switch (BindToKernelMethodMissing(metaBuilder, args, methodName, methodMissing, incompatibleVisibility, isSuperCall)) { case MethodMissingBinding.Custom: // we pretend we found the member and return a method that calls method_missing: Debug.Assert(!metaBuilder.Error); metaBuilder.Result = Methods.CreateBoundMissingMember.OpCall( AstUtils.Convert(args.TargetExpression, typeof(object)), Ast.Constant(methodMissing, typeof(RubyMemberInfo)), Ast.Constant(methodName) ); return true; case MethodMissingBinding.Error: // method_missing is defined in Kernel, error has been reported: return true; case MethodMissingBinding.Fallback: // method_missing is defined in Kernel: if (defaultFallback) { metaBuilder.SetError(Methods.MakeMissingMemberError.OpCall(Ast.Constant(methodName))); return true; } return false; } throw Assert.Unreachable; }
internal static MethodResolutionResult Resolve(MetaObjectBuilder/*!*/ metaBuilder, string/*!*/ methodName, CallArguments/*!*/ args, out RubyMemberInfo methodMissing) { MethodResolutionResult method; var targetClass = args.TargetClass; var visibilityContext = GetVisibilityContext(args.Signature, args.Scope); using (targetClass.Context.ClassHierarchyLocker()) { metaBuilder.AddTargetTypeTest(args.Target, targetClass, args.TargetExpression, args.MetaContext, new[] { methodName, Symbols.MethodMissing } ); if (args.Signature.IsSuperCall) { Debug.Assert(!args.Signature.IsVirtualCall && args.Signature.HasImplicitSelf); method = targetClass.ResolveSuperMethodNoLock(methodName, targetClass).InvalidateSitesOnOverride(); } else { var options = args.Signature.IsVirtualCall ? MethodLookup.Virtual : MethodLookup.Default; method = targetClass.ResolveMethodForSiteNoLock(methodName, visibilityContext, options); } if (!method.Found) { methodMissing = targetClass.ResolveMethodMissingForSite(methodName, method.IncompatibleVisibility); } else { methodMissing = null; } } // Whenever the current self's class changes we need to invalidate the rule, if a protected method is being called. if (method.Info != null && method.Info.IsProtected && visibilityContext.Class != null) { // We don't need to compare versions, just the class objects (super-class relationship cannot be changed). // Since we don't want to hold on a class object (to make it collectible) we compare references to the version handlers. metaBuilder.AddCondition(Ast.Equal( Methods.GetSelfClassVersionHandle.OpCall(AstUtils.Convert(args.MetaScope.Expression, typeof(RubyScope))), Ast.Constant(visibilityContext.Class.Version) )); } return method; }
private bool TryGetClrEvent(Type/*!*/ type, BindingFlags bindingFlags, bool inherited, string/*!*/ name, string altName, out RubyMemberInfo method) { return TryGetClrEvent(type, bindingFlags, inherited, name, out method) ? true : altName != null && TryGetClrEvent(type, bindingFlags, inherited, altName, out method); }
private bool TryGetClrEvent(Type/*!*/ type, BindingFlags bindingFlags, string/*!*/ name, out RubyMemberInfo method) { Assert.NotNull(type, name); EventInfo eventInfo = type.GetEvent(name, bindingFlags); if (eventInfo != null) { // creates detached info if only declared members are requested (used by Kernel#clr_member): bool createDetached = (bindingFlags & BindingFlags.DeclaredOnly) != 0; method = new RubyEventInfo((EventTracker)MemberTracker.FromMemberInfo(eventInfo), RubyMemberFlags.Public, this, createDetached); return true; } method = null; return false; }
private bool TryGetClrEvent(Type/*!*/ type, BindingFlags bindingFlags, bool inherited, string/*!*/ name, out RubyMemberInfo method) { Assert.NotNull(type, name); EventInfo eventInfo; if (inherited) { eventInfo = type.GetInheritedEvents(name).WithBindingFlags(bindingFlags).FirstOrDefault(); } else { eventInfo = type.GetDeclaredEvent(name).WithBindingFlags(bindingFlags); } if (eventInfo != null) { // creates detached info if only declared members are requested (used by Kernel#clr_member): method = new RubyEventInfo((EventTracker)MemberTracker.FromMemberInfo(eventInfo), RubyMemberFlags.Public, this, isDetached: inherited); return true; } method = null; return false; }
// thread safe: doesn't need any lock since it only accesses immutable state public bool TryGetClrMember(string/*!*/ name, out RubyMemberInfo method) { // Get the first class in hierarchy that represents CLR type - worse case we end up with Object. // Ruby classes don't represent a CLR type and hence expose no CLR members. RubyClass cls = this; while (cls.TypeTracker == null) { cls = cls.SuperClass; } Debug.Assert(!cls.TypeTracker.Type.IsInterface); // Note: We don't cache failures as this API is not used so frequently (e.g. for regular method dispatch) that we would need caching. method = null; return cls.TryGetClrMember(cls.TypeTracker.Type, name, true, 0, out method); }
internal override void PrepareMethodUpdate(string/*!*/ methodName, RubyMemberInfo/*!*/ method) { PrepareMethodUpdate(methodName, method, 0); }
// thread safe: doesn't need any lock since it only accesses immutable state public bool TryGetClrConstructor(out RubyMemberInfo method) { ConstructorInfo[] ctors; if (TypeTracker != null && !TypeTracker.Type.IsInterface && (ctors = TypeTracker.Type.GetConstructors()) != null && ctors.Length > 0) { method = new RubyMethodGroupInfo(ctors, this, true); return true; } method = null; return false; }
// thread safe: doesn't need any lock since it only accesses immutable state public bool TryGetClrMember(string/*!*/ name, Type asType, out RubyMemberInfo method) { Debug.Assert(!_isSingletonClass); // Get the first class in hierarchy that represents CLR type - worse case we end up with Object. // Ruby classes don't represent a CLR type and hence expose no CLR members. RubyClass cls = this; while (cls.TypeTracker == null) { cls = cls.SuperClass; } Type type = cls.TypeTracker.Type; Debug.Assert(!RubyModule.IsModuleType(type)); // Note: We don't cache results as this API is not used so frequently (e.g. for regular method dispatch). if (asType != null && !asType.IsAssignableFrom(type)) { throw RubyExceptions.CreateNameError(String.Format("`{0}' does not inherit from `{1}'", cls.Name, Context.GetTypeName(asType, true))); } method = null; using (Context.ClassHierarchyLocker()) { return cls.TryGetClrMember(asType ?? type, name, true, true, true, out method); } }
protected override bool TryGetClrMember(Type/*!*/ type, string/*!*/ name, bool tryUnmangle, out RubyMemberInfo method) { Context.RequiresClassHierarchyLock(); if (IsFailureCached(type, name, _isSingletonClass)) { method = null; return false; } if (TryGetClrMember(type, name, tryUnmangle, BindingFlags.DeclaredOnly, out method)) { return true; } CacheFailure(type, name, _isSingletonClass); method = null; return false; }
protected override bool TryGetClrMember(Type/*!*/ type, string/*!*/ name, bool mapNames, bool unmangleNames, out RubyMemberInfo method) { Context.RequiresClassHierarchyLock(); if (IsFailureCached(type, name, _isSingletonClass, _extensionVersion)) { method = null; return false; } if (TryGetClrMember(type, name, mapNames, unmangleNames, false, out method)) { return true; } CacheFailure(type, name, _isSingletonClass, _extensionVersion); method = null; return false; }
private bool TryGetClrMember(Type/*!*/ type, string/*!*/ name, bool tryUnmangle, BindingFlags basicBindingFlags, out RubyMemberInfo method) { basicBindingFlags |= BindingFlags.Public | BindingFlags.NonPublic; // We look only for members directly declared on the type and handle method overloads inheritance manually. BindingFlags bindingFlags = basicBindingFlags | ((_isSingletonClass) ? BindingFlags.Static : BindingFlags.Instance); // instance methods on Object are also available in static context: if (type == typeof(Object)) { bindingFlags |= BindingFlags.Instance; } string operatorName; if (tryUnmangle && !_isSingletonClass && (operatorName = RubyUtils.MapOperator(name)) != null) { // instance invocation of an operator: if (TryGetClrMethod(type, basicBindingFlags | BindingFlags.Static, true, name, null, operatorName, null, out method)) { return true; } } else if (tryUnmangle && (name == "[]" || name == "[]=")) { if (type.IsArray && !_isSingletonClass) { bool isSetter = name.Length == 3; TryGetClrMethod(type, bindingFlags, false, name, null, isSetter ? "Set" : "Get", null, out method); Debug.Assert(method != null); return true; } else { object[] attrs = type.GetCustomAttributes(typeof(DefaultMemberAttribute), false); if (attrs.Length == 1) { // default indexer accessor: bool isSetter = name.Length == 3; if (TryGetClrProperty(type, bindingFlags, isSetter, name, ((DefaultMemberAttribute)attrs[0]).MemberName, null, out method)) { return true; } } } } else if (name.LastCharacter() == '=') { string propertyName = name.Substring(0, name.Length - 1); string altName = tryUnmangle ? RubyUtils.TryUnmangleName(propertyName) : null; // property setter: if (TryGetClrProperty(type, bindingFlags, true, name, propertyName, altName, out method)) return true; // writeable field: if (TryGetClrField(type, bindingFlags, true, propertyName, altName, out method)) return true; } else { string altName = tryUnmangle ? RubyUtils.TryUnmangleName(name) : null; // method: if (TryGetClrMethod(type, bindingFlags, false, name, null, name, altName, out method)) return true; // getter: if (TryGetClrProperty(type, bindingFlags, false, name, name, altName, out method)) return true; // event: if (TryGetClrEvent(type, bindingFlags, name, altName, out method)) return true; // field: if (TryGetClrField(type, bindingFlags, false, name, altName, out method)) return true; } method = null; return false; }
/// <summary> /// There are basically 4 cases: /// 1) CLR method of the given name is not defined in the specified type. /// Do nothing, the method will be found as we traverse the hierarhy towards the Kernel module. /// 2) Otherwise /// 1) There is no RubyMemberInfo of given <c>name</c> present in the (type..Kernel] ancestors. /// We need to search all types in (type..Object] for CLR method overloads. /// 2) There is a RubyMemberInfo in a class, say C, in (type..Kernel]. /// We need to get CLR methods from (type..C) in addition to the members in the type. /// 1) C.HidesInheritedOverloads == true /// All overloads of the method we look for are in [type..C). /// 2) C.HidesInheritedOverloads == false /// All overloads of the method we look for are in [type..C) and in the RubyMemberInfo. /// </summary> /// <remarks> /// Doesn't include explicitly implemented interface methods. Including them would allow to call them directly (obj.foo) /// if the overload resolution succeeds. However, the interface methods are probably implemented explicitly for a reason: /// 1) There is a conflict in signatures -> the overload resolution would probably fail. /// 2) The class was designed with an intention to not expose the implementations directly. /// </remarks> private bool TryGetClrMethod(Type/*!*/ type, BindingFlags bindingFlags, bool inherited, bool specialNameOnly, string/*!*/ name, string clrNamePrefix, string/*!*/ clrName, string altClrName, out RubyMemberInfo method) { Context.RequiresClassHierarchyLock(); // declared only: List<OverloadInfo> initialMembers = new List<OverloadInfo>(GetClrMethods(type, bindingFlags, inherited, clrNamePrefix, clrName, altClrName, specialNameOnly)); if (initialMembers.Count == 0) { // case [1] // // Note: This failure might be cached (see CacheFailure) based on the type and name, // therefore it must not depend on any other mutable state: method = null; return false; } // If all CLR inherited members are to be returned we are done. // (creates a detached info; used by Kernel#clr_member) if (inherited) { method = MakeGroup(initialMembers, initialMembers.Count, specialNameOnly, true); return true; } // inherited overloads: List<RubyClass> ancestors = new List<RubyClass>(); RubyMemberInfo inheritedRubyMember = null; bool skipHidden = false; ForEachAncestor((module) => { if (module != this) { if (module.TryGetDefinedMethod(name, ref skipHidden, out inheritedRubyMember) && !inheritedRubyMember.IsSuperForwarder) { return true; } // Skip classes that have no tracker, e.g. Fixnum(tracker) <: Integer(null) <: Numeric(null) <: Object(tracker). // Skip CLR modules, their methods are not callable => do not include them into a method group. // Skip all classes once hidden sentinel is encountered (no CLR overloads are visible since then). if (!skipHidden && module.TypeTracker != null && module.IsClass) { ancestors.Add((RubyClass)module); } } // continue: return false; }); // (method clr name, parameter types) => (overload, owner) Dictionary<Key<string, ValueArray<Type>>, ClrOverloadInfo> allMethods = null; if (inheritedRubyMember != null) { // case [2.2.2]: add CLR methods from the Ruby member: var inheritedGroup = inheritedRubyMember as RubyOverloadGroupInfo; if (inheritedGroup != null) { AddMethodsOverwriteExisting(ref allMethods, inheritedGroup.MethodBases, inheritedGroup.OverloadOwners, specialNameOnly); } else if (inheritedRubyMember.IsRemovable) { // The groups created below won't contain overloads defined above (if there are any). // If this method is removed we need to invalidate them. inheritedRubyMember.InvalidateGroupsOnRemoval = true; } } // populate classes in (type..Kernel] or (type..C) with method groups: for (int i = ancestors.Count - 1; i >= 0; i--) { var declared = ancestors[i].GetClrMethods(ancestors[i].TypeTracker.Type, bindingFlags, false, clrNamePrefix, clrName, altClrName, specialNameOnly); if (AddMethodsOverwriteExisting(ref allMethods, declared, null, specialNameOnly)) { // There is no cached method that needs to be invalidated. // // Proof: // Suppose the group being created here overridden an existing method that is cached in a dynamic site invoked on some target class. // Then either the target class is above all ancestors[i] or below some. If it is above then the new group doesn't // invalidate validity of the site. If it is below then the method resolution for the cached method would create // and store to method tables all method groups in between the target class and the owner of the cached method, including the // one that contain overloads of ancestors[i]. But no module below inheritedRubyMember contains a method group of the name // being currently resolved. ancestors[i].AddMethodNoCacheInvalidation(name, ancestors[i].MakeGroup(allMethods.Values)); } } if (allMethods != null) { // add members declared in self: AddMethodsOverwriteExisting(ref allMethods, initialMembers, null, specialNameOnly); // return the group, it will be stored in the method table by the caller: method = MakeGroup(allMethods.Values); } else { method = MakeGroup(initialMembers, initialMembers.Count, specialNameOnly, false); } return true; }
// TODO: Indexers can be overloaded: //private bool TryGetClrProperty(Type/*!*/ type, BindingFlags bindingFlags, string/*!*/ name, bool isWrite, out RubyMemberInfo method) { // Assert.NotNull(type, name); // PropertyInfo propertyInfo = type.GetProperty(name, bindingFlags); // if (propertyInfo != null) { // MethodInfo accessor = isWrite ? propertyInfo.GetSetMethod(false) : propertyInfo.GetGetMethod(false); // if (accessor != null) { // // TODO: define RubyPropertyInfo class // method = new RubyMethodGroupInfo(new MethodBase[] { accessor }, this, _isSingletonClass); // return true; // } // } // method = null; // return false; //} private bool TryGetClrProperty(Type/*!*/ type, BindingFlags bindingFlags, bool inherited, bool isWrite, string/*!*/ name, string/*!*/ clrName, string altClrName, out RubyMemberInfo method) { return TryGetClrMethod(type, bindingFlags, inherited, true, name, isWrite ? "set_" : "get_", clrName, altClrName, out method); }
private bool TryGetClrField(Type/*!*/ type, BindingFlags bindingFlags, bool isWrite, string/*!*/ name, string altName, out RubyMemberInfo method) { return TryGetClrField(type, bindingFlags, isWrite, name, out method) ? true : altName != null && TryGetClrField(type, bindingFlags, isWrite, altName, out method); }
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 ) ); }