Reflection is in!

Hi,

Ok, it’s still a bit WIP, but the core of the reflection system is now in!

It introduces 3 new ‘builtin’ types:

I’ve added a simple reflecttest banana too.

Variants

The ‘Variant’ type is a primitive type that can be used to ‘box’ values of any type.

The easiest way to create a variant is to cast a value to Variant (much like casting an Int to String etc), eg:

An uninitialized variant will contain a ‘null’ value (of type Void) until you assign something to it:

A variant is ‘true’ if it contains any value with a non-void type (including a bool false value!) and ‘false’ if it is uninitialized and has no (void) type.

Any type of value can be implicitly converted to a variant, so you can easily pass anything to a function with variant parameters:

To retrieve the value contained in a variant, you must explicitly cast the variant to the desired type:

Note that the cast must specify the exact type of the value already contained in the variant, or a runtime error will occur:

The one exception to this is if the Variant contains a class object, in which case you can cast the variant to any valid base class of the object.

Typeof and TypeInfo

The Typeof operator returns a TypeInfo object that contains various properties and methods for inspecting types at runtime. There are 2 ways to use Typeof:

The use of seperate () and <> delimeters is to prevent the parser getting confused by complex expressions/types.

TypeInfo objects have a To:String operator (mainly for debugging) so can be printed directly:

Typeof returns the ‘static’ type of a class object. To get the actual instance type, use the Object.InstanceType property:

You can retrieve the type of the value contained in a variant using the Variant.Type property:

TypeInfo also includes functions for inspecting all user defined types:

Returns the TypeInfo for a named type. A named type is a namespace or class declared by your app – it does not include primitive types, pointer types, array types etc. Class names must be prefixed by the namespace they are declared in.

To get an array of ALL named types:

DeclInfo objects

TypeInfo objects for namespaces and classes also contain a set of DeclInfo objects. A DeclInfo represents the member declarations inside of a namespace or class. Currently, only global, field, method and function members are supported. DeclInfo objects also have a To:String operator to help with debugging.

You can inspect the member decls of a type using the TypeInfo.GetDecls method:

You can retrieve a single unique member using TypeInfo.GetDecl:

There may be several decls with the same name due to method and function overloading, in which case the simple GetDecl above will fail and return null. In this case, you either need to inspect each decl individually to find the one you want, or you can pass an additional TypeInfo parameter to GetDecl:

This will return the default constructor for MyClass, assuming there is one.

Getting and setting variables

Member decls that represent variables (ie: fields and globals) can be read and written using the DeclInfo.Get and Decl.Info.Set methods:

The first parameter of Set and Get is an object instance, which must be non-null for getting and setting fields.

The second parameter of Set is a variant, and is the value to assign to the variable. The type of the value contained in the variant must match the variable type exactly, or a runtime error will occur.

Note that since any value can be cast to a variant, we can just provide the literal value ’10’ for Set and it will be implictly converted to a variant for us. On the other hand, we must explicitly cast the result of Get() from a variant back to the type of value we want.

Invoking methods and functions

To invoke methods and functions, use the DeclInfo.Invoke method:

The first parameter of Invoke is an object instance, which must be non-null for invoking methods.

The second parameter of Invoke is an array of variants that represents the parameters for the call. The types of these parameters must match the parameter types of the actual method or function exactly, or a runtime error will occur.

Limitations

Currently, typeinfo is only generated for non-generic, non-extension, non-extern 100% pure monkey2 globals, fields, function, methods, classes and namespaces. You can still use other types (structs etc) with variants etc, but you wont be able to inspect their members.

Typeinfo may be stripped out by the linker. I’ve added a little hack to mojo to keep module typeinfo ‘alive’, but there is still work to do here. If you find the linker stripping out typeinfo, you can prevent it doing so for now by adding a ‘Typeof’ to Main() referencing the type you want to keep alive. Or, you can set MX2_WHOLE_ARCHIVE in bin/env_blah.txt to ‘1’ to force the linker to include ALL code, but this will of course produce larger executables.

User comments
Ethernaut says:

Great stuff, Mark! My only complaint is that my build sizes are almost twice as large compared to 1.0.8, and compiling anything for the first time seems slower. Could all the new reflection stuff be causing it? Can it be disabled (not imported) if unnecessary, like in Monkey 1? Thanks!