-
Notifications
You must be signed in to change notification settings - Fork 86
using defer when metaobjects have no value
Binders should always protect themselves by checking whether all the DynamicMetaObjects passed to their FallbackX methods have values (see HasValue property). As a conservative implementation, if any meta-object is lacking a concrete value, then call the binder's Defer method on all the meta-objects passed in. The Defer method results in a DynamicMetaObject with an Expression that creates a nested CallSite. This allows the DynamicMetaObject expressions to be evaluated and to flow into the nested site with values. If binders did not protect themselves in this way, an infinite loop results as they produce a rule that fails when the CallSite executes it, which forces a binding update, which causes the same target DynamicMetaObject to fallback with no value, which causes the binder to produce a bad rule, and so on.
Let's look at why DynamicMetaObjects might not have values and then look at a real situation in the Sympl code that infinitely loops without HasValue checks. Sometimes dynamic languages produce partial results or return rules for performing part of an operation but then need the language binder to do the rest. A prime example when interoperating with IronPython is how it handles InvokeMember. It will fetch the member, package it in an IronPython callable object represented as an IDynamicMetaObjectProvider, and then call FallbackInvoke on your binder. The dynamic object has no value, just an expression capturing how to get the callable object. Anytime a language or DynamicMetaObject needs to return a dynamic object to a FallbackX method, it should never place a value in the DynamicMetaObject it passes to the FallbackX method. Doing so would cause the FallbackX method to try to do static .NET binding on the object, but of course, that's not right since the static nature of the object is the carrier for the dynamic nature.
For a concrete example within Sympl's implementation, consider this line of code from indexing.sympl:
(set l (new (System.Collections.Generic.List`1.MakeGenericType
types)))
Without SymplInvokeMemberBinder.FallbackInvokeMember testing whether the DynamicMetaObjects passed to it have values and deferring, it would infinitely loop with TypeModelMetaObject.BindInvokeMember for the "MakeGenericType" member. BindINvokeMember would fall back with no value (as shown below), and the binder would produce a binding result whose rule restrictions would fail. The CallSite would then try to update the rule. The TypeModelMetaObject would fall back again with no value, and this would repeat forever.
Before adding the HasValue check to the Sympl binders, the runtime helper function GetRuntimeTypeMoFromModel had to supply a value to the meta-object it produced. This is not always possible or the right thing to do, but it worked for GetRuntimeTypeMoFromModel because it could produce a regular .NET static object for the binder that was consistent with an instance restriction on the type object.
public static DynamicMetaObject GetRuntimeTypeMoFromModel
(DynamicMetaObject typeModelMO) {
Debug.Assert((typeModelMO.LimitType == typeof(TypeModel)),
"Internal: MO is not a TypeModel?!");
// Get tm.ReflType
var pi = typeof(TypeModel).GetProperty("ReflType");
Debug.Assert(pi != null);
return new DynamicMetaObject(
Expression.Property(
Expression.Convert(typeModelMO.Expression,
typeof(TypeModel)),
pi),
typeModelMO.Restrictions.Merge(
BindingRestrictions.GetTypeRestriction(
typeModelMO.Expression, typeof(TypeModel)))//,
//((TypeModel)typeModelMO.Value).ReflType
);
When the highlight code above gets comment out, the code below is what prevents the FallbackInvokeMember function from infintely looping through the CallSite, trying to bind with the TypeModel's meta-object:
public override DynamicMetaObject FallbackInvokeMember(
DynamicMetaObject targetMO, DynamicMetaObject[] args,
DynamicMetaObject errorSuggestion) {
// ... code deleted for example ...
if (!targetMO.HasValue || args.Any((a) => !a.HasValue)) {
var deferArgs = new DynamicMetaObject[args.Length + 1];
for (int i = 0; i < args.Length; i++) {
deferArgs[i + 1] = args[i];
}
deferArgs[0] = targetMO;
return Defer(deferArgs);
Every FallbackX method on all your binders should protect themselves by checking all arguments for HasValue. If HasValue is false for any, then call Defer as shown in the Sympl binders. Note, the above code is the most complicated, and for FallbackGetMember, it is just this:
if (!targetMO.HasValue) return Defer(targetMO);
Frontmatter
1 Introduction
1.1 Sources
1.2 Walkthrough Organization
2 Quick Language Overview
3 Walkthrough of Hello World
3.1 Quick Code Overview
3.2 Hosting, Globals, and .NET Namespaces Access
3.2.1 DLR Dynamic Binding and Interoperability -- a Very Quick Description
3.2.2 DynamicObjectHelpers
3.2.3 TypeModels and TypeModelMetaObjects
3.2.4 TypeModelMetaObject's BindInvokeMember -- Finding a Binding
3.2.5 TypeModelMetaObject.BindInvokeMember -- Restrictions and Conversions
3.3 Import Code Generation and File Module Scopes
3.4 Function Call and Dotted Expression Code Generation
3.4.1 Analyzing Function and Member Invocations
3.4.2 Analyzing Dotted Expressions
3.4.3 What Hello World Needs
3.5 Identifier and File Globals Code Generation
3.6 Sympl.ExecuteFile and Finally Running Code
4 Assignment to Globals and Locals
5 Function Definition and Dynamic Invocations
5.1 Defining Functions
5.2 SymplInvokeBinder and Binding Function Calls
6 CreateThrow Runtime Binding Helper
7 A Few Easy, Direct Translations to Expression Trees
7.1 Let* Binding
7.2 Lambda Expressions and Closures
7.3 Conditional (IF) Expressions
7.4 Eq Expressions
7.5 Loop Expressions
8 Literal Expressions
8.1 Integers and Strings
8.2 Keyword Constants
8.3 Quoted Lists and Symbols
8.3.1 AnalyzeQuoteExpr -- Code Generation
8.3.2 Cons and List Keyword Forms and Runtime Support
9 Importing Sympl Libraries and Accessing and Invoking Their Globals
10 Type instantiation
10.1 New Keyword Form Code Generation
10.2 Binding CreateInstance Operations in TypeModelMetaObject
10.3 Binding CreateInstance Operations in FallbackCreateInstance
10.4 Instantiating Arrays and GetRuntimeTypeMoFromModel
11 SymplGetMemberBinder and Binding .NET Instance Members
12 ErrorSuggestion Arguments to Binder FallbackX Methods
13 SymplSetMemberBinder and Binding .NET Instance Members
14 SymplInvokeMemberBinder and Binding .NET Member Invocations
14.1 FallbackInvokeMember
14.2 FallbackInvoke
15 Indexing Expressions: GetIndex and SetIndex
15.1 SymplGetIndexBinder's FallbackGetIndex
15.2 GetIndexingExpression
15.3 SymplSetIndexBinder's FallbackSetIndex
16 Generic Type Instantiation
17 Arithmetic, Comparison, and Boolean Operators
17.1 Analysis and Code Generation for Binary Operations
17.2 Analysis and Code Generation for Unary Operations
17.3 SymplBinaryOperationBinder
17.4 SymplUnaryOperationBinder
18 Canonical Binders or L2 Cache Sharing
19 Binding COM Objects
20 Using Defer When MetaObjects Have No Value
21 SymPL Language Description
21.1 High-level
21.2 Lexical Aspects
21.3 Built-in Types
21.4 Control Flow
21.4.1 Function Call
21.4.2 Conditionals
21.4.3 Loops
21.4.4 Try/Catch/Finally and Throw
21.5 Built-in Operations
21.6 Globals, Scopes, and Import
21.6.1 File Scopes and Import
21.6.2 Lexical Scoping
21.6.3 Closures
21.7 Why No Classes
21.8 Keywords
21.9 Example Code (mostly from test.sympl)
22 Runtime and Hosting
22.1 Class Summary
23 Appendixes
23.1 Supporting the DLR Hosting APIs
23.1.1 Main and Example Host Consumer
23.1.2 Runtime.cs Changes
23.1.3 Sympl.cs Changes
23.1.4 Why Not Show Using ScriptRuntime.Globals Namespace Reflection
23.1.5 The New DlrHosting.cs File
23.2 Using the Codeplex.com DefaultBinder for rich .NET interop
23.3 Using Codeplex.com Namespace/Type Trackers instead of ExpandoObjects
23.4 Using Codeplex.com GeneratorFunctionExpression
Other documents:
Dynamic Language Runtime
DLR Hostirng Spec
Expression Trees v2 Spec
Getting Started with the DLR as a Library Author
Sites, Binders, and Dynamic Object Interop Spec