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

10 Type instantiation

Sympl has a new keyword form. It takes as its first argument an expression that results in a type. Sympl code can get types in one of two ways, importing them from the hosting globals table in the Sympl runtime instance or from the result of a .NET call. One code path has to bind Sympl's TypeModel objects to constructors (TypeModelMetaObject.BindCreateInstance), and the other handles more direct .NET binding (SymplCreateInstanceBinder's FallbackCreateInstance method). The rest of the arguments to the new keyword form are used to find an appropriate constructor to call.

10.1 New Keyword Form Code Generation

The analysis and code generation for New is pretty easy since all the work is in runtime binders and the TypeModelMetaObject. Here's the code from etgen.cs:

public static Expression AnalyzeNewExpr(SymplNewExpr expr,

AnalysisScope scope) {

List<Expression> args = new List<Expression>();

args.Add(AnalyzeExpr(expr.Type, scope));

args.AddRange(expr.Arguments.Select(

a => AnalyzeExpr(a, scope)));

return Expression.Dynamic(

scope.GetRuntime().GetCreateInstanceBinder(

new CallInfo(expr.Arguments.Length)),

typeof(object),

args);

AnalyzeNewExpr just analyzes the type expression and all the arguments to build a list of arguments for a DynamicExpression with a SymplCreateInstanceBinder. The metadata for binding is a description of the arguments. For Sympl, this is just an argument count, but note that the count does not include the target type expression even though it is in the 'args' variable passed to the Dynamic factory method.

For now, ignore GetCreateInstanceBinder. Imagine this is just a call to the constructor:

new SymplCreateInstanceBinder(CallInfo)

GetCreateInstanceBinder produces canonical binders, a single binder instance used on every call site with the same metadata. This is important for DLR L2 caching of rules. See section for how Sympl makes canonical binders and why, and see sites-binders-dynobj-interop.doc for more details on CallSite rule caching.

This DynamicExpression has a result type of object. You might think Sympl could statically type this CallSite to the type of the instance being created. However, the type is unknown until run time when some expression results in a first class type object. Therefore, as with all Dynamic expressions in Sympl, the type is object.

10.2 Binding CreateInstance Operations in TypeModelMetaObject

One path that type instantiation can take in Sympl is from code like the following:

(set x (new System.Text.StringBuilder "hello"))

In this case one of Sympl's TypeModel objects (representing StringBuilder) flows into the CallSite into which AnalyzeNewExpr's CreateInstance DynamicExpression compiles. Then TypeModelMetaObject's BindCreateInstance produces a rule for creating the StringBuilder.

Here's the code from sympl.cs (the python code is in runtime.py):

public override DynamicMetaObject BindCreateInstance(

CreateInstanceBinder binder, DynamicMetaObject[] args) {

var constructors = ReflType.GetConstructors();

var ctors = constructors.

Where(c => c.GetParameters().Length == args.Length);

List<ConstructorInfo> res = new List<ConstructorInfo>();

foreach (var c in ctors) {

if (RuntimeHelpers.ParametersMatchArguments(

c.GetParameters(),

args)) {

res.Add(c);

}

}

if (res.Count == 0) {

return binder.FallbackCreateInstance(

RuntimeHelpers.GetRuntimeTypeMoFromModel(this),

args);

}

var restrictions = RuntimeHelpers.GetTargetArgsRestrictions(

this, args, true);

var ctorArgs =

RuntimeHelpers.ConvertArguments(

args, res[0].GetParameters());

return new DynamicMetaObject(

Expression.New(res[0], ctorArgs),

restrictions);

First BindCreateInstance gets the underlying RuntimeType's constructors and finds those with marching parameter counts. Then Sympl filters for constructors with matching parameters as discussed in section on TypeModelMetaObject's BindInvokeMember method.

If no constructors match, then Sympl falls back to the language binder after converting the TypeModel to a meta-object representing the RuntimeType object. See the sub section below on instantiating arrays for information on GetRuntimeTypeMoFromModel. Falling back may seem futile, but in addition to other language binders having richer matching rules that might succeed, the convention is to fall back to the binder to get a language-specific error for failing to bind.

Finally, BindCreateInstance gathers restrictions for the rule it produces and converts the arguments, as discussed in section for TypeModelMetaObject's BindInvokeMember method. Then BindCreateInstance returns the DynamicMetaObject whose restrictions and Expression property (using the Expression Tree New factory) form a rule for creating instances of the target type. The resulting expression does not need to go through EnsureObjectResult since creating an instance necessarily returns objects.

10.3 Binding CreateInstance Operations in FallbackCreateInstance

One path that type instantiation can take in Sympl is from code like the following:

;; x is a StringBuilder instance from the previous section

(set y (new (x.GetType) (x.ToString)))

In this case one of the DLR's default DynamicMetaObjects for static .NET objects (representing the RuntimeType object for StringBuilder) calls the SymplCreateInstanceBinder's FallbackCreateInstance method.

Here's the code from runtime.cs:

public class SymplCreateInstanceBinder : CreateInstanceBinder {

public SymplCreateInstanceBinder(CallInfo callinfo)

: base(callinfo) {

}

public override DynamicMetaObject FallbackCreateInstance(

DynamicMetaObject target,

DynamicMetaObject[] args,

DynamicMetaObject errorSuggestion) {

// ... Deleted checking for Defer for now ...

if (!typeof(Type).IsAssignableFrom(target.LimitType)) {

return errorSuggestion ??

RuntimeHelpers.CreateThrow(

target, args, BindingRestrictions.Empty,

typeof(InvalidOperationException),

"Type object must be used when " +

"creating instance -- " +

args.ToString());

}

var type = target.Value as Type;

Debug.Assert(type != null);

var constructors = type.GetConstructors();

// Get constructors with right arg counts.

var ctors = constructors.

Where(c => c.GetParameters().Length == args.Length);

List<ConstructorInfo> res = new List<ConstructorInfo>();

foreach (var c in ctors) {

if (RuntimeHelpers.ParametersMatchArguments(

c.GetParameters(),

args)) {

res.Add(c);

}

}

var restrictions =

RuntimeHelpers.GetTargetArgsRestrictions(

target, args, true);

if (res.Count == 0) {

return errorSuggestion ??

RuntimeHelpers.CreateThrow(

target, args, restrictions,

typeof(MissingMemberException),

"Can't bind create instance -- " +

args.ToString());

}

var ctorArgs =

RuntimeHelpers.ConvertArguments(

args, res[0].GetParameters());

return new DynamicMetaObject(

Expression.New(res[0], ctorArgs),

restrictions);

Let's first talk about what we aren't talking about now. This code snippet omits some very important code that protects binders and DynamicMetaObjects from infinitely looping due to producing bad rules. It is best to discuss this in one place, so see section for how the infinite loop happens and how to prevent it for all binders.

This coded is nearly the same as TypeModelMetaObject's BindCreateInstance discussed in the previous section. One difference to note is that while DynamicMetaObjects can fall back to binders for errors or potentially more binding searching, the binder creates Throw expressions. FallbackCreateInstance has to gather the target and argument restrictions before deciding to return an error DynamicMetaObject result so that it can ensure it uses the same restrictions it would use in a positive result. See section for a discussion of CreateThrow and restrictions.

10.4 Instantiating Arrays and GetRuntimeTypeMoFromModel

Because Sympl has no built-in notion of arrays (similar to IronPython), you create arrays in Sympl like you would in IronPython:

(System.Array.CreateInstance System.String 3)

This expression turns into an InvokeMember DynamicExpression. The resulting CallSite gets a Sympl TypeModel object for System.String. This is an example of why ConvertArguments and GetTargetArgsRestrictions conspire to match TypeModel objects to parameters of type Type and convert the former to the latter, as discussed in section .

Here's the code for RuntimeHelpers.GetRuntimeTypeMoFromModel from runtime.cs, which converts a DynamicMetaObject holding a TypeModel value to one holding a RuntimeType value:

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

The key here is to refrain from lifting the RuntimeType value out of the TypeModel and burning it into the rule as a ConstantExpression. Instead this function returns an Expression in the DynamicMetaObject that fetches the ReflType property from the result of the Expression in the TypeModel's meta-object. This allows the rule to work more generally.

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