Skip to content

using defer when metaobjects have no value

Stéphane Lozier edited this page Jan 18, 2021 · 1 revision

20 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);

SymPL Implementation on the Dynamic Language Runtime

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

Clone this wiki locally