/// <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> /// 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 ); }
// Normalizes arguments: inserts self, expands splats, and inserts rhs arg. // Adds any restrictions/conditions applied to the arguments to the given meta-builder. internal static DynamicMetaObject[]/*!*/ NormalizeArguments(MetaObjectBuilder/*!*/ metaBuilder, CallArguments/*!*/ args, SelfCallConvention callConvention, bool calleeHasBlockParam, bool injectMissingBlockParam) { var result = new List<DynamicMetaObject>(); // self (instance): if (callConvention == SelfCallConvention.SelfIsInstance) { result.Add(args.MetaTarget); } // block: if (calleeHasBlockParam) { if (args.Signature.HasBlock) { if (args.GetMetaBlock() == null) { // the user explicitly passed nil as a block arg: result.Add(RubyBinder.NullMetaObject); } else { // pass BlockParam: Debug.Assert(metaBuilder.BfcVariable != null); result.Add(new DynamicMetaObject(metaBuilder.BfcVariable, BindingRestrictions.Empty)); } } else { // no block passed into a method with a BlockParam: result.Add(RubyBinder.NullMetaObject); } } else if (injectMissingBlockParam) { // no block passed into a method w/o a BlockParam (we still need to fill the missing block argument): result.Add(RubyBinder.NullMetaObject); } // self (parameter): if (callConvention == SelfCallConvention.SelfIsParameter) { result.Add(args.MetaTarget); } // simple arguments: for (int i = 0; i < args.SimpleArgumentCount; i++) { result.Add(args.GetSimpleMetaArgument(i)); } // splat argument: int listLength; ParameterExpression listVariable; if (args.Signature.HasSplattedArgument) { var splatted = args.GetSplattedMetaArgument(); if (metaBuilder.AddSplattedArgumentTest(splatted.Value, splatted.Expression, out listLength, out listVariable)) { // AddTestForListArg only returns 'true' if the argument is a List<object> var list = (List<object>)splatted.Value; // get arguments, add tests for (int j = 0; j < listLength; j++) { result.Add(DynamicMetaObject.Create( list[j], Ast.Call(listVariable, typeof(List<object>).GetMethod("get_Item"), AstUtils.Constant(j)) )); } } else { // argument is not an array => add the argument itself: result.Add(splatted); } } // rhs argument: if (args.Signature.HasRhsArgument) { result.Add(args.GetRhsMetaArgument()); } return result.ToArray(); }
/// <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 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; }