subclassable

EXPERIMENTAL

Allows you to make a class available to the script rather than just class objects. You can subclass it in script and then call the methods again through the original D interface. With caveats...

More...
subclassable
(
T
)
()
if (
is(T == class) ||
is(T == interface)
)

Detailed Description

Assumes ALL virtual methods and constructors are scriptable, but requires @scriptable to be present on final or static methods. This may change in the future.

Note that it has zero support for @safe, pure, nothrow, and other attributes at this time and will skip that use those. I may be able to loosen this in the future as well but I have no concrete plan to at this time. You can still mark them as @scriptable to call them from the script, but they can never be overridden by script code because it cannot verify those guarantees hold true.

Ditto on const and immutable.

Its behavior on overloads is currently undefined - it may keep only any random overload as the only one and do dynamic type conversions to cram data into it. This is likely to change in the future but for now try not to use this on classes with overloaded methods.

It also does not wrap member variables unless explicitly marked @scriptable; it is meant to communicate via methods.

Examples

Demonstrates tested capabilities of subclassable

1 interface IFoo {
2 	string method();
3 	int method2();
4 	int args(int, int);
5 }
6 // note the static is just here because this
7 // is written in a unittest; it shouldn't actually
8 // be necessary under normal circumstances.
9 static class Foo : IFoo {
10 	ulong handle() { return cast(ulong) cast(void*) this; }
11 	string method() { return "Foo"; }
12 	int method2() { return 10; }
13 	int args(int a, int b) {
14 		//import std.stdio; writeln(a, " + ", b, " + ", member_, " on ", cast(ulong) cast(void*) this);
15 		return member_+a+b; }
16 
17 	int member_;
18 	@property int member(int i) { return member_ = i; }
19 	@property int member() { return member_; }
20 
21 	@scriptable final int fm() { return 56; }
22 }
23 static class Bar : Foo {
24 	override string method() { return "Bar"; }
25 }
26 static class Baz : Bar {
27 	override int method2() { return 20; }
28 }
29 
30 static class WithCtor {
31 	// constructors work but are iffy with overloads....
32 	this(int arg) { this.arg = arg; }
33 	@scriptable int arg; // this is accessible cuz it is @scriptable
34 	int getValue() { return arg; }
35 }
36 
37 var globals = var.emptyObject;
38 globals.Foo = subclassable!Foo;
39 globals.Bar = subclassable!Bar;
40 globals.Baz = subclassable!Baz;
41 globals.WithCtor = subclassable!WithCtor;
42 
43 import arsd.script;
44 
45 interpret(q{
46 	// can instantiate D classes added via subclassable
47 	var foo = new Foo();
48 	// and call its methods...
49 	assert(foo.method() == "Foo");
50 	assert(foo.method2() == 10);
51 
52 	foo.member(55);
53 
54 	// proves the new operator actually creates new D
55 	// objects as well to avoid sharing instance state.
56 	var foo2 = new Foo();
57 	assert(foo2.handle() != foo.handle());
58 
59 	// passing arguments works
60 	assert(foo.args(2, 4) == 6 + 55); // (and sanity checks operator precedence)
61 
62 	var bar = new Bar();
63 	assert(bar.method() == "Bar");
64 	assert(bar.method2() == 10);
65 
66 	// this final member is accessible because it was marked @scriptable
67 	assert(bar.fm() == 56);
68 
69 	// the script can even subclass D classes!
70 	class Amazing : Bar {
71 		// and override its methods
72 		var inst = 99;
73 		function method() {
74 			return "Amazing";
75 		}
76 
77 		// note: to access instance members or virtual call lookup you MUST use the `this` keyword
78 		// otherwise the function will be called with scope limited to this class itself (similar to javascript)
79 		function other() {
80 			// this.inst is needed to get the instance variable (otherwise it would only look for a static var)
81 			// and this.method triggers dynamic lookup there, so it will get children's overridden methods if there is one
82 			return this.inst ~ this.method();
83 		}
84 
85 		function args(a, b) {
86 			// calling parent class method still possible
87 			return super.args(a*2, b*2);
88 		}
89 	}
90 
91 	var amazing = new Amazing();
92 	assert(amazing.method() == "Amazing");
93 	assert(amazing.method2() == 10); // calls back to the parent class
94 	amazing.member(5);
95 
96 	// this line I can paste down to interactively debug the test btw.
97 	//}, globals); repl!true(globals); interpret(q{
98 
99 	assert(amazing.args(2, 4) == 12+5);
100 
101 	var wc = new WithCtor(5); // argument passed to constructor
102 	assert(wc.getValue() == 5);
103 
104 	// confirm the property read works too
105 	assert(wc.arg == 5);
106 
107 	// but property WRITING is currently not working though.
108 
109 
110 	class DoubleChild : Amazing {
111 		function method() {
112 			return "DoubleChild";
113 		}
114 	}
115 
116 	// can also do a child of a child class
117 	var dc = new DoubleChild();
118 	assert(dc.method() == "DoubleChild");
119 	assert(dc.other() == "99DoubleChild"); // the `this.method` means it uses the replacement now
120 	assert(dc.method2() == 10); // back to the D grandparent
121 	assert(dc.args(2, 4) == 12); // but the args impl from above
122 }, globals);
123 
124 Foo foo = globals.foo.get!Foo; // get the native object back out
125 assert(foo.member == 55); // and see mutation via properties proving object mutability
126 assert(globals.foo.get!Bar is null); // cannot get the wrong class out of it
127 assert(globals.foo.get!Object !is null); // but can do parent classes / interfaces
128 assert(globals.foo.get!IFoo !is null);
129 assert(globals.bar.get!Foo !is null); // the Bar can also be a Foo
130 
131 Bar amazing = globals.amazing.get!Bar; // instance of the script's class is still accessible through parent D class or interface
132 assert(amazing !is null); // object exists
133 assert(amazing.method() == "Amazing"); // calls the override from the script
134 assert(amazing.method2() == 10); // non-overridden function works as expected
135 
136 IFoo iamazing = globals.amazing.get!IFoo; // and through just the interface works the same way
137 assert(iamazing !is null);
138 assert(iamazing.method() == "Amazing");
139 assert(iamazing.method2() == 10);

Meta

History

Added April 25, 2020

Suggestion Box / Bug Report