internal void AddArgumentRestrictions(MetaObjectBuilder /*!*/ metaBuilder, BindingTarget /*!*/ bindingTarget) { var args = GetActualArguments(); var restrictedArgs = bindingTarget.Success ? bindingTarget.RestrictedArguments.GetObjects() : args.Arguments; for (int i = _firstRestrictedArg; i < restrictedArgs.Count; i++) { var arg = (bindingTarget.Success ? restrictedArgs[i] : restrictedArgs[i].Restrict(restrictedArgs[i].GetLimitType())); if (i >= args.FirstSplattedArg && i <= _lastSplattedArg) { metaBuilder.AddCondition(arg.Restrictions.ToExpression()); } else { metaBuilder.AddRestriction(arg.Restrictions); } } // Adds condition for collapsed arguments - it is the same whether we succeed or not: var splatCondition = GetCollapsedArgsCondition(); if (splatCondition != null) { metaBuilder.AddCondition(splatCondition); } }
/// <summary> /// Takes current result and wraps it into try-filter(MethodUnwinder)-finally block that ensures correct "break" behavior for /// library method calls with block given in bfcVariable (BlockParam). /// </summary> public static void RuleControlFlowBuilder(MetaObjectBuilder /*!*/ metaBuilder, CallArguments /*!*/ args) { if (metaBuilder.Error) { return; } var metaBlock = args.GetMetaBlock(); Debug.Assert(metaBlock != null, "RuleControlFlowBuilder should only be used if the signature has a block"); // We construct CF only for non-nil blocks thus we need a test for it: if (metaBlock.Value == null) { metaBuilder.AddRestriction(Ast.Equal(metaBlock.Expression, AstUtils.Constant(null))); return; } // don't need to test the exact type of the Proc since the code is subclass agnostic: metaBuilder.AddRestriction(Ast.NotEqual(metaBlock.Expression, AstUtils.Constant(null))); Expression bfcVariable = metaBuilder.BfcVariable; Debug.Assert(bfcVariable != null); // Method call with proc can invoke control flow that returns an arbitrary value from the call, so we need to type result to Object. // Otherwise, the result could only be result of targetExpression unless its return type is void. Expression resultVariable = metaBuilder.GetTemporary(typeof(object), "#result"); ParameterExpression unwinder; metaBuilder.Result = Ast.Block( Ast.Assign(bfcVariable, Methods.CreateBfcForLibraryMethod.OpCall(AstUtils.Convert(args.GetBlockExpression(), typeof(Proc)))), AstUtils.Try( Ast.Assign(resultVariable, AstUtils.Convert(metaBuilder.Result, typeof(object))) ).Filter(unwinder = Ast.Parameter(typeof(MethodUnwinder), "#unwinder"), Methods.IsProcConverterTarget.OpCall(bfcVariable, unwinder), Ast.Assign(resultVariable, Ast.Field(unwinder, MethodUnwinder.ReturnValueField)), AstUtils.Default(typeof(object)) ).Finally( Methods.LeaveProcConverter.OpCall(bfcVariable) ), resultVariable ); }
internal void BuildInvoke(MetaObjectBuilder/*!*/ metaBuilder, CallArguments/*!*/ args) { Assert.NotNull(metaBuilder, args); Debug.Assert(args.Target == this); // TODO: we could compare infos here: // first argument must be this method: metaBuilder.AddRestriction(Ast.Equal(args.TargetExpression, AstUtils.Constant(this))); // set the target (becomes self in the called method): args.SetTarget(AstUtils.Constant(_target), _target); _info.BuildCall(metaBuilder, args, _name); }
internal virtual void BuildInvoke(MetaObjectBuilder/*!*/ metaBuilder, CallArguments/*!*/ args) { Assert.NotNull(metaBuilder, args); Debug.Assert(args.Target == this); // first argument must be this method: metaBuilder.AddRestriction(Ast.Equal(args.TargetExpression, AstUtils.Constant(this))); // set the target (becomes self in the called method): args.SetTarget(AstUtils.Constant(_target, CompilerHelpers.GetVisibleType(_target)), _target); _info.BuildCall(metaBuilder, args, _name); }
public static Expression[]/*!*/ MakeActualArgs(MetaObjectBuilder/*!*/ metaBuilder, CallArguments/*!*/ args, bool includeSelf, bool selfIsInstance, bool calleeHasBlockParam, bool injectMissingBlockParam) { var actualArgs = new List<Expression>(args.ExplicitArgumentCount); // self (instance): if (includeSelf && selfIsInstance) { // test already added by method resolver Debug.Assert(args.TargetExpression != null); AddArgument(actualArgs, args.Target, args.TargetExpression); } Proc block = null; Expression blockExpression = null; // block test - we need to test for a block regardless of whether it is actually passed to the method or not // since the information that the block is not null is used for overload resolution. if (args.Signature.HasBlock) { block = args.GetBlock(); blockExpression = args.GetBlockExpression(); if (block == null) { metaBuilder.AddRestriction(Ast.Equal(blockExpression, Ast.Constant(null))); } else { // don't need to test the exact type of the Proc since the code is subclass agnostic: metaBuilder.AddRestriction(Ast.NotEqual(blockExpression, Ast.Constant(null))); } } // block: if (calleeHasBlockParam) { if (args.Signature.HasBlock) { if (block == null) { // the user explicitly passed nil as a block arg: actualArgs.Add(Ast.Constant(null)); } else { // pass BlockParam: Debug.Assert(metaBuilder.BfcVariable != null); actualArgs.Add(metaBuilder.BfcVariable); } } else { // no block passed into a method with a BlockParam: actualArgs.Add(Ast.Constant(null)); } } else if (injectMissingBlockParam) { // no block passed into a method w/o a BlockParam (we still need to fill the missing block argument): actualArgs.Add(Ast.Constant(null)); } // self (non-instance): if (includeSelf && !selfIsInstance) { // test already added by method resolver AddArgument(actualArgs, args.Target, args.TargetExpression); } // simple arguments: for (int i = 0; i < args.SimpleArgumentCount; i++) { var value = args.GetSimpleArgument(i); var expr = args.GetSimpleArgumentExpression(i); metaBuilder.AddObjectTypeRestriction(value, expr); AddArgument(actualArgs, value, expr); } // splat argument: int listLength; ParameterExpression listVariable; if (args.Signature.HasSplattedArgument) { object splattedArg = args.GetSplattedArgument(); Expression splattedArgExpression = args.GetSplattedArgumentExpression(); if (metaBuilder.AddSplattedArgumentTest(splattedArg, splattedArgExpression, out listLength, out listVariable)) { // AddTestForListArg only returns 'true' if the argument is a List<object> var list = (List<object>)splattedArg; // get arguments, add tests for (int j = 0; j < listLength; j++) { var value = list[j]; var expr = Ast.Call(listVariable, typeof(List<object>).GetMethod("get_Item"), Ast.Constant(j)); metaBuilder.AddObjectTypeCondition(value, expr); AddArgument(actualArgs, value, expr); } } else { // argument is not an array => add the argument itself: AddArgument(actualArgs, splattedArg, splattedArgExpression); } } // rhs argument: if (args.Signature.HasRhsArgument) { var value = args.GetRhsArgument(); var expr = args.GetRhsArgumentExpression(); metaBuilder.AddObjectTypeRestriction(value, expr); AddArgument(actualArgs, value, expr); } return actualArgs.ToArray(); }
public static Expression[] /*!*/ MakeActualArgs(MetaObjectBuilder /*!*/ metaBuilder, CallArguments /*!*/ args, bool includeSelf, bool selfIsInstance, bool calleeHasBlockParam, bool injectMissingBlockParam) { var actualArgs = new List <Expression>(args.ExplicitArgumentCount); // self (instance): if (includeSelf && selfIsInstance) { // test already added by method resolver Debug.Assert(args.TargetExpression != null); AddArgument(actualArgs, args.Target, args.TargetExpression); } Proc block = null; Expression blockExpression = null; // block test - we need to test for a block regardless of whether it is actually passed to the method or not // since the information that the block is not null is used for overload resolution. if (args.Signature.HasBlock) { block = args.GetBlock(); blockExpression = args.GetBlockExpression(); if (block == null) { metaBuilder.AddRestriction(Ast.Equal(blockExpression, Ast.Constant(null))); } else { // don't need to test the exact type of the Proc since the code is subclass agnostic: metaBuilder.AddRestriction(Ast.NotEqual(blockExpression, Ast.Constant(null))); } } // block: if (calleeHasBlockParam) { if (args.Signature.HasBlock) { if (block == null) { // the user explicitly passed nil as a block arg: actualArgs.Add(Ast.Constant(null)); } else { // pass BlockParam: Debug.Assert(metaBuilder.BfcVariable != null); actualArgs.Add(metaBuilder.BfcVariable); } } else { // no block passed into a method with a BlockParam: actualArgs.Add(Ast.Constant(null)); } } else if (injectMissingBlockParam) { // no block passed into a method w/o a BlockParam (we still need to fill the missing block argument): actualArgs.Add(Ast.Constant(null)); } // self (non-instance): if (includeSelf && !selfIsInstance) { // test already added by method resolver AddArgument(actualArgs, args.Target, args.TargetExpression); } // simple arguments: for (int i = 0; i < args.SimpleArgumentCount; i++) { var value = args.GetSimpleArgument(i); var expr = args.GetSimpleArgumentExpression(i); metaBuilder.AddObjectTypeRestriction(value, expr); AddArgument(actualArgs, value, expr); } // splat argument: int listLength; ParameterExpression listVariable; if (args.Signature.HasSplattedArgument) { object splattedArg = args.GetSplattedArgument(); Expression splattedArgExpression = args.GetSplattedArgumentExpression(); if (metaBuilder.AddSplattedArgumentTest(splattedArg, splattedArgExpression, out listLength, out listVariable)) { // AddTestForListArg only returns 'true' if the argument is a List<object> var list = (List <object>)splattedArg; // get arguments, add tests for (int j = 0; j < listLength; j++) { var value = list[j]; var expr = Ast.Call(listVariable, typeof(List <object>).GetMethod("get_Item"), Ast.Constant(j)); metaBuilder.AddObjectTypeCondition(value, expr); AddArgument(actualArgs, value, expr); } } else { // argument is not an array => add the argument itself: AddArgument(actualArgs, splattedArg, splattedArgExpression); } } // rhs argument: if (args.Signature.HasRhsArgument) { var value = args.GetRhsArgument(); var expr = args.GetRhsArgumentExpression(); metaBuilder.AddObjectTypeRestriction(value, expr); AddArgument(actualArgs, value, expr); } return(actualArgs.ToArray()); }
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 ) ); }
internal void AddArgumentRestrictions(MetaObjectBuilder/*!*/ metaBuilder, BindingTarget/*!*/ bindingTarget) { var args = GetActualArguments(); var restrictedArgs = bindingTarget.Success ? bindingTarget.RestrictedArguments.GetObjects() : args.Arguments; for (int i = _firstRestrictedArg; i < restrictedArgs.Count; i++) { var arg = (bindingTarget.Success ? restrictedArgs[i] : restrictedArgs[i].Restrict(restrictedArgs[i].GetLimitType())); if (i >= args.FirstSplattedArg && i <= _lastSplattedArg) { metaBuilder.AddCondition(arg.Restrictions.ToExpression()); } else { metaBuilder.AddRestriction(arg.Restrictions); } } // Adds condition for collapsed arguments - it is the same whether we succeed or not: var splatCondition = GetCollapsedArgsCondition(); if (splatCondition != null) { metaBuilder.AddCondition(splatCondition); } }
private Expression/*!*/ MarshalArgument(MetaObjectBuilder/*!*/ metaBuilder, DynamicMetaObject/*!*/ arg, ArgType parameterType) { object value = arg.Value; if (value == null) { metaBuilder.AddRestriction(Ast.Equal(arg.Expression, AstUtils.Constant(null))); } else { metaBuilder.AddTypeRestriction(value.GetType(), arg.Expression); } switch (parameterType) { case ArgType.Buffer: if (value == null) { return AstUtils.Constant(null, typeof(byte[])); } if (value is int && (int)value == 0) { metaBuilder.AddRestriction(Ast.Equal(AstUtils.Convert(arg.Expression, typeof(int)), AstUtils.Constant(0))); return AstUtils.Constant(null, typeof(byte[])); } if (value.GetType() == typeof(MutableString)) { return Methods.GetMutableStringBytes.OpCall( AstUtils.Convert(arg.Expression, typeof(MutableString)) ); } return Methods.GetMutableStringBytes.OpCall( AstUtils.LightDynamic(ConvertToStrAction.Make(_context), typeof(MutableString), arg.Expression) ); case ArgType.Int32: if (value is int) { return AstUtils.Convert(arg.Expression, typeof(int)); } return Ast.Convert( Ast.Call( AstUtils.LightDynamic(ConvertToIntAction.Make(_context), typeof(IntegerValue), arg.Expression), Methods.IntegerValue_ToUInt32Unchecked ), typeof(int) ); } throw Assert.Unreachable; }
private void BuildCall(MetaObjectBuilder/*!*/ metaBuilder, CallArguments/*!*/ args, string/*!*/ name) { var actualArgs = RubyOverloadResolver.NormalizeArguments(metaBuilder, args, 0, Int32.MaxValue); if (metaBuilder.Error) { return; } metaBuilder.AddRestriction( Ast.Equal( Ast.Property(Ast.Convert(args.TargetExpression, typeof(Win32API)), VersionProperty), Ast.Constant(_version) ) ); if (_function == IntPtr.Zero) { metaBuilder.SetError(Ast.Throw(new Func<Exception>(UninitializedFunctionError).Method.OpCall(), typeof(object))); return; } if (_signature.Length != actualArgs.Count) { metaBuilder.SetError(Ast.Throw(new Func<int, int, Exception>(InvalidParameterCountError).Method.OpCall( Ast.Constant(_signature.Length), Ast.Constant(actualArgs.Count)), typeof(object) )); return; } var calliArgs = new AstExpressions(); calliArgs.Add(Ast.Property(Ast.Convert(args.TargetExpression, typeof(Win32API)), FunctionProperty)); for (int i = 0; i < actualArgs.Count; i++) { calliArgs.Add(MarshalArgument(metaBuilder, actualArgs[i], _signature[i])); } metaBuilder.Result = Ast.Call(EmitCalliStub(), calliArgs); // MRI returns 0 if void return type is given: if (_returnType == ArgType.None) { metaBuilder.Result = Ast.Block(metaBuilder.Result, AstUtils.Constant(0)); } }
/// <summary> /// Takes current result and wraps it into try-filter(MethodUnwinder)-finally block that ensures correct "break" behavior for /// library method calls with block given in bfcVariable (BlockParam). /// </summary> public static void RuleControlFlowBuilder(MetaObjectBuilder/*!*/ metaBuilder, CallArguments/*!*/ args) { if (metaBuilder.Error) { return; } var metaBlock = args.GetMetaBlock(); Debug.Assert(metaBlock != null, "RuleControlFlowBuilder should only be used if the signature has a block"); // We construct CF only for non-nil blocks thus we need a test for it: if (metaBlock.Value == null) { metaBuilder.AddRestriction(Ast.Equal(metaBlock.Expression, AstUtils.Constant(null))); return; } // don't need to test the exact type of the Proc since the code is subclass agnostic: metaBuilder.AddRestriction(Ast.NotEqual(metaBlock.Expression, AstUtils.Constant(null))); Expression bfcVariable = metaBuilder.BfcVariable; Debug.Assert(bfcVariable != null); // Method call with proc can invoke control flow that returns an arbitrary value from the call, so we need to type result to Object. // Otherwise, the result could only be result of targetExpression unless its return type is void. Expression resultVariable = metaBuilder.GetTemporary(typeof(object), "#result"); ParameterExpression unwinder; metaBuilder.Result = Ast.Block( Ast.Assign(bfcVariable, Methods.CreateBfcForLibraryMethod.OpCall(AstUtils.Convert(args.GetBlockExpression(), typeof(Proc)))), AstUtils.Try( Ast.Assign(resultVariable, AstUtils.Convert(metaBuilder.Result, typeof(object))) ).Filter(unwinder = Ast.Parameter(typeof(MethodUnwinder), "#unwinder"), Methods.IsProcConverterTarget.OpCall(bfcVariable, unwinder), Ast.Assign(resultVariable, Ast.Field(unwinder, MethodUnwinder.ReturnValueField)), AstUtils.Default(typeof(object)) ).Finally( Methods.LeaveProcConverter.OpCall(bfcVariable) ), resultVariable ); }
/// <summary> /// Resolves an library method overload and builds call expression. /// The resulting expression on meta-builder doesn't handle block control flow yet. /// </summary> internal static void BuildCallNoFlow(MetaObjectBuilder/*!*/ metaBuilder, CallArguments/*!*/ args, string/*!*/ name, IList<MethodBase>/*!*/ overloads, SelfCallConvention callConvention) { var bindingTarget = ResolveOverload(name, overloads, args, callConvention); bool calleeHasBlockParam = bindingTarget.Success && HasBlockParameter(bindingTarget.Method); // Allocates a variable holding BlockParam. At runtime the BlockParam is created with a new RFC instance that // identifies the library method frame as a proc-converter target of a method unwinder triggered by break from a block. if (args.Signature.HasBlock) { var metaBlock = args.GetMetaBlock(); if (metaBlock.Value != null && calleeHasBlockParam) { if (metaBuilder.BfcVariable == null) { metaBuilder.BfcVariable = metaBuilder.GetTemporary(typeof(BlockParam), "#bfc"); } metaBuilder.ControlFlowBuilder = RuleControlFlowBuilder; } // Block test - we need to test for a block regardless of whether it is actually passed to the method or not // since the information that the block is not null is used for overload resolution. if (metaBlock.Value == null) { metaBuilder.AddRestriction(Ast.Equal(metaBlock.Expression, AstUtils.Constant(null))); } else { // don't need to test the exact type of the Proc since the code is subclass agnostic: metaBuilder.AddRestriction(Ast.NotEqual(metaBlock.Expression, AstUtils.Constant(null))); } } var actualArgs = MakeActualArgs(metaBuilder, args, callConvention, calleeHasBlockParam, true); if (bindingTarget.Success) { var parameterBinder = new RubyParameterBinder(args.RubyContext.Binder, args.MetaContext.Expression, args.Signature.HasScope); metaBuilder.Result = bindingTarget.MakeExpression(parameterBinder, actualArgs); } else { metaBuilder.SetError(args.RubyContext.RubyBinder.MakeInvalidParametersError(bindingTarget).Expression); } }
internal void AddFullVersionTest(MetaObjectBuilder/*!*/ metaBuilder, MSA.Expression/*!*/ contextExpression) { Assert.NotNull(metaBuilder); EnsureInitialized(); // Initialization changes the version number, so ensure that the module is initialized // check for runtime: metaBuilder.AddRestriction(Ast.Equal(contextExpression, Ast.Constant(_context))); // check for version: metaBuilder.AddCondition(Ast.Equal(Ast.Property(Ast.Constant(this), VersionProperty), Ast.Constant(_version))); }
internal static BindingTarget/*!*/ ResolveOverload(MetaObjectBuilder/*!*/ metaBuilder, CallArguments/*!*/ args, string/*!*/ name, IList<MethodBase>/*!*/ overloads, SelfCallConvention callConvention, out RubyOverloadResolver/*!*/ resolver) { resolver = new RubyOverloadResolver(metaBuilder, args, callConvention); var bindingTarget = resolver.ResolveOverload(name, overloads, NarrowingLevel.None, NarrowingLevel.All); bool calleeHasBlockParam = bindingTarget.Success && HasBlockParameter(bindingTarget.Method); // At runtime the BlockParam is created with a new RFC instance that identifies the library method frame as // a proc-converter target of a method unwinder triggered by break from a block. if (args.Signature.HasBlock) { var metaBlock = args.GetMetaBlock(); if (metaBlock.Value != null && calleeHasBlockParam) { Debug.Assert(metaBuilder.BfcVariable != null); metaBuilder.ControlFlowBuilder = RuleControlFlowBuilder; } // Overload resolution might not need to distinguish between nil and non-nil block. // However, we still do since we construct CF only for non-nil blocks. if (metaBlock.Value == null) { metaBuilder.AddRestriction(Ast.Equal(metaBlock.Expression, AstUtils.Constant(null))); } else { // don't need to test the exact type of the Proc since the code is subclass agnostic: metaBuilder.AddRestriction(Ast.NotEqual(metaBlock.Expression, AstUtils.Constant(null))); } } // add restrictions used for overload resolution: resolver.AddArgumentRestrictions(metaBuilder, bindingTarget); return bindingTarget; }