Skip to content

indexing expressions getindex and setindex

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

15 Indexing Expressions: GetIndex and SetIndex

Sympl supports indexing its built-in lists, arrays, and indexers/indexed properties. Expression Trees v1 had an ArrayIndex factory that would return either a BinaryExpression for single-dimensional arrays or a MethodCallExpression for multi-dimensional arrays. These now exist only for LINQ backward compatibility. All new code should use the ArrayAccess or MakeIndex factories that return IndexExpressions. Expression Trees v2 support IndexExpressions everywhere, including the left hand side of assignments and as byref arguments.

SymplGetIndexBinder and SymplSetIndexBinder both use the RuntimeHelpers method GetIndexingExpression. It does most of the work for FallbackGetIndex. FallbackSetIndex has to do some extra work.

15.1 SymplGetIndexBinder's FallbackGetIndex

Here's the code from runtime.cs, which is described further below:

public override DynamicMetaObject FallbackGetIndex(

DynamicMetaObject target, DynamicMetaObject[] indexes,

DynamicMetaObject errorSuggestion) {

// ... Deleted checking for COM and need to Defer for now ...

// Give good error for Cons.

if (target.LimitType == typeof(Cons)) {

if (indexes.Length != 1)

return errorSuggestion ??

RuntimeHelpers.CreateThrow(

target, indexes, BindingRestrictions.Empty,

typeof(InvalidOperationException),

"Indexing list takes single index. " +

"Got " + indexes.Length.ToString());

}

var indexingExpr =

RuntimeHelpers.EnsureObjectResult(

RuntimeHelpers.GetIndexingExpression(target,

indexes));

var restrictions = RuntimeHelpers.GetTargetArgsRestrictions(

target, indexes, false);

return new DynamicMetaObject(indexingExpr, restrictions);

Let's first talk about what we aren't talking about now. This code snippet omits the code to check if the target is a COM object and to use built-in COM support. See section for information adding this to your binders. The snippet also 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.

As we said before, GetIndexingExpression does most of the work here. Before calling it, FallbackGetIndex checks if the indexing is for Sympl built-in lists and whether the argument count is right. After calling GetIndexingExpression, the expression passes through EnsureObjectResult in case it needs to be wrapped to ensure it is strictly typed as assignable to object. For more information, see section 3.2.4. FallbackGetIndex uses the binding helper GetTargetArgsRestrictions to get restrictions and returns the resulting DynamicMetaObject with the indexing expression and restrictions.

15.2 GetIndexingExpression

SymplGetIndexBinder and SymplSetIndexBinder both use the RuntimeHelpers method GetIndexingExpression. It does most of the work FallbackGetIndex. FallbackSetIndex has to do some extra work.

Here's the code from RuntimeHelpers in runtime.cs, which is further explained below:

public static Expression GetIndexingExpression(

DynamicMetaObject target,

DynamicMetaObject[] indexes) {

Debug.Assert(target.HasValue &&

target.LimitType != typeof(Array));

var indexExpressions = indexes.Select(

i => Expression.Convert(i.Expression, i.LimitType))

.ToArray();

// HANDLE CONS

if (target.LimitType == typeof(Cons)) {

// Call RuntimeHelper.GetConsElt

var args = new List<Expression>();

// The first argument is the list

args.Add(

Expression.Convert(

target.Expression,

target.LimitType)

);

args.AddRange(indexExpressions);

return Expression.Call(

typeof(RuntimeHelpers),

"GetConsElt",

null,

args.ToArray());

// HANDLE ARRAY

} else if (target.LimitType.IsArray) {

// the target has an array type

return Expression.ArrayAccess(

Expression.Convert(target.Expression,

target.LimitType),

indexExpressions

);

// HANDLE INDEXERS

} else {

var props = target.LimitType.GetProperties();

var indexers = props.

Where(p => p.GetIndexParameters().Length > 0).ToArray();

indexers = indexers.

Where(idx => idx.GetIndexParameters().Length ==

indexes.Length).ToArray();

var res = new List<PropertyInfo>();

foreach (var idxer in indexers) {

if (RuntimeHelpers.ParametersMatchArguments(

idxer.GetIndexParameters(),

indexes)) {

// all parameter types match

res.Add(idxer);

}

}

if (res.Count == 0) {

return Expression.Throw(

Expression.New(

typeof(MissingMemberException)

.GetConstructor(new Type[]

{ typeof(string) }),

Expression.Constant(

"Can't bind because there is no " +

"matching indexer.")

)

);

}

return Expression.MakeIndex(

Expression.Convert(target.Expression, target.LimitType),

res[0], indexExpressions);

The first thing GetIndexingExpression does is get ConvertExpressions for all the indexing arguments. It converts them to the specific LimitType of the DynamicMetaObject. See section for a discussion of using LimitType over RuntimeType. It may seem odd to convert the object to the type that LimitType reports it to be, but the type of the meta-object's expression might be more general and require an explicit Convert node to satisfy the strict typing of the Expression Tree factory or the actual emitted code that executes. The Expression Tree compiler removes unnecessary Convert nodes.

The first kind of indexing Sympl supports is its built-in lists (target's LimitType is Cons). In this case the GetIndexingExpression creates a ConvertExpression for the target to its LimitType, just like the index arguments discussed above. Then it takes the converted target and argument expressions to create a MethodCallExpression for RuntimeHelpers.GetConsElt. You can see this runtime helper in runtime.cs.

The second kind of indexing Sympl supports is arrays (target's LimitType IsArray). In this case GetIndexingExpression also creates a ConvertExpression for the target to its LimitType. Then it takes the converted target and argument expressions to create an IndexExpression.

The third kind of indexing Sympl supports is looking for an indexer or indexed property. GetIndexingExpression gets the target's properties and filters for those whose parameter count matches the index arguments count. Then it filters for matching parameter types, which is described in section . If no properties match, GetIndexingExpression returns a Throw expression (doesn't use CreateThrow here since it returns a DynamicMetaObject). Finally GetIndexingExpression calls MakeIndex to return an IndexExpression. It also converts the target to its LimitType, as discussed above.

15.3 SymplSetIndexBinder's FallbackSetIndex

Here's the code from runtime.cs, which is described further below:

public override DynamicMetaObject FallbackSetIndex(

DynamicMetaObject target, DynamicMetaObject[] indexes,

DynamicMetaObject value,

DynamicMetaObject errorSuggestion) {

// ... Deleted checking for COM and need to Defer for now ...

Expression valueExpr = value.Expression;

if (value.LimitType == typeof(TypeModel)) {

valueExpr = RuntimeHelpers.GetRuntimeTypeMoFromModel(value)

.Expression;

}

Debug.Assert(target.HasValue &&

target.LimitType != typeof(Array));

Expression setIndexExpr;

if (target.LimitType == typeof(Cons)) {

if (indexes.Length != 1) {

return errorSuggestion ??

RuntimeHelpers.CreateThrow(

target, indexes, BindingRestrictions.Empty,

typeof(InvalidOperationException),

"Indexing list takes single index. " +

"Got " + indexes);

}

// Call RuntimeHelper.SetConsElt

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

// The first argument is the list

args.Add(

Expression.Convert(

target.Expression,

target.LimitType)

);

// The second argument is the index.

args.Add(Expression.Convert(indexes[0].Expression,

indexes[0].LimitType));

// The last argument is the value

args.Add(Expression.Convert(valueExpr, typeof(object)));

// Sympl helper returns value stored.

setIndexExpr = Expression.Call(

typeof(RuntimeHelpers),

"SetConsElt",

null,

args.ToArray());

} else {

Expression indexingExpr =

RuntimeHelpers.GetIndexingExpression(target,

indexes);

setIndexExpr = Expression.Assign(indexingExpr, valueExpr);

}

BindingRestrictions restrictions =

RuntimeHelpers.GetTargetArgsRestrictions(target, indexes,

false);

return new DynamicMetaObject(

RuntimeHelpers.EnsureObjectResult(setIndexExpr),

restrictions);

Let's first talk about what we aren't talking about now. This code snippet omits the code to check if the target is a COM object and to use built-in COM support. See section for information adding this to your binders. The snippet also 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.

At a high level FallbackSetIndex examines the value meta-object, then gets an expression to set the index, then forms restrictions, and lastly returns the resulting DynamicMetaObject representing the bound operation. The value processing is just a check for whether to convert TypeModel to a RuntimeType meta-object. See section for a discussion of the restrictions. The only difference here is the false value to get a type restriction on the target.

To determine the indexing expression, FallbackSetIndex checks for the target being a Cons. If it is, the binder needs to call the RuntimeHelpers.SetConsElt method. The binder first checks the number of arguments and whether it should call CreateThrow. See section for a discussion of CreateThrow and restrictions. As discussed with GetIndexingExpression, the binder creates a ConvertExpression to convert the target to its LimitType, and does the same for the index arguments. The binder converts the value to the Type object because that's what the runtime helper takes. Finally, the resulting indexing expression for setting a Cons element is a MethodCallExpression for SetConsElt. This helper returns the value stored to be in compliance with the convention for binders and meta-objects.

In the alternative branch, FallbackSetIndex uses the GetIndexingExpression binding helper. The binder then wraps that in an Assign node. This guarantees returning the value stored also.

In either case, the operation implementation expression passes through EnsureObjectResult in case it needs to be wrapped to ensure it is strictly typed as assignable to object. For more information, see section 3.2.4.

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