diff --git a/include/hx/Scriptable.h b/include/hx/Scriptable.h index 7a605dc01..887a2c3c7 100644 --- a/include/hx/Scriptable.h +++ b/include/hx/Scriptable.h @@ -220,6 +220,26 @@ void __Visit(HX_VISIT_PARAMS) HXCPP_OVERRIDE { super::__Visit(HX_VISIT_ARG); ::h #endif +/* + * A Haxe `toString` with parameters, whatever they are, compiles to a C++ overload that + * hides `hx::Object::toString()` without overriding it. In a class that inherits it, + * `__superString` is that class, so `__superString::toString()` does not compile. + * Native code calling `toString()` on such an object reaches `hx::Object::toString()`, + * so the scriptable wrapper calls that one in this case. + */ +template +inline auto _hx_scriptable_super_toString(T *inSelf, int) -> decltype(inSelf->T::toString(), ::String()) +{ + return inSelf->T::toString(); +} + +template +inline ::String _hx_scriptable_super_toString(T *inSelf, ...) +{ + return inSelf->::hx::Object::toString(); +} + + #define HX_DEFINE_SCRIPTABLE(ARG_LIST) \ inline void *operator new( size_t inSize, int inExtraDataSize ) \ { \ @@ -235,7 +255,7 @@ void __Visit(HX_VISIT_PARAMS) HXCPP_OVERRIDE { super::__Visit(HX_VISIT_ARG); ::h void ** __GetScriptVTable() HXCPP_OVERRIDE { return __scriptVTable; } \ ::String toString() HXCPP_OVERRIDE { if (__scriptVTable[0] ) \ { ::hx::CppiaCtx *ctx = ::hx::CppiaCtx::getCurrent(); ::hx::AutoStack a(ctx); ctx->pushObject(this); return ctx->runString(__scriptVTable[0]); } \ - else return __superString::toString(); } \ + else return _hx_scriptable_super_toString<__superString>(static_cast<__superString *>(this), 0); } \ ::String __ToString() const HXCPP_OVERRIDE { return ::hx::ScriptableToString(__scriptVTable[-1]); } \ ::hx::Class __GetClass() const HXCPP_OVERRIDE { return ::hx::ScriptableGetClass(__scriptVTable[-1]); } \ int __GetType() const HXCPP_OVERRIDE { return ::hx::ScriptableGetType(__scriptVTable[-1]); } \ diff --git a/test/cppia/Client.hx b/test/cppia/Client.hx index 710373f4b..c62c41020 100644 --- a/test/cppia/Client.hx +++ b/test/cppia/Client.hx @@ -14,6 +14,13 @@ class ClientFoo implements IFoo { } } +// None overrides toString: the scriptable wrapper falls back on the host's. +class ClientOptionalToString extends OptionalToString.OptionalToStringChild {} + +class ClientRequiredToString extends OptionalToString.RequiredToStringChild {} + +class ClientZeroArgToString extends OptionalToString.ZeroArgToString {} + class Client { public static var clientBool0 = true; diff --git a/test/cppia/OptionalToString.hx b/test/cppia/OptionalToString.hx new file mode 100644 index 000000000..3c4fc2dac --- /dev/null +++ b/test/cppia/OptionalToString.hx @@ -0,0 +1,32 @@ +/** + * Scriptable hosts whose `toString` takes parameters, which hide `hx::Object::toString()` + * in C++, and one whose `toString` takes none. The children inherit a hiding `toString`, + * so the scriptable wrapper generated for them cannot call `toString()` on its parent. + */ +class OptionalToString { + public function new() {} + + public function toString(indent:Int = 0):String { + return "indent" + indent; + } +} + +class RequiredToString { + public function new() {} + + public function toString(indent:Int, prefix:String):String { + return prefix + indent; + } +} + +class ZeroArgToString { + public function new() {} + + public function toString():String { + return "zero"; + } +} + +class OptionalToStringChild extends OptionalToString {} + +class RequiredToStringChild extends RequiredToString {} diff --git a/test/cppia/cases/TestCommon.hx b/test/cppia/cases/TestCommon.hx index eaacb5a76..d97385956 100644 --- a/test/cppia/cases/TestCommon.hx +++ b/test/cppia/cases/TestCommon.hx @@ -59,6 +59,15 @@ class TestCommon extends Test { Assert.equals(2, Common.callbackSet, 'Bad cppia closure'); } + @:depends(testStatus) + function testHostToStringWithParameters() { + // As in native code, a toString with parameters is not the one Std.string calls. + Assert.equals('OptionalToStringChild', Std.string(new OptionalToString.OptionalToStringChild())); + Assert.equals('ClientOptionalToString', Std.string(Type.createInstance(Type.resolveClass('ClientOptionalToString'), []))); + Assert.equals('ClientRequiredToString', Std.string(Type.createInstance(Type.resolveClass('ClientRequiredToString'), []))); + Assert.equals('zero', Std.string(Type.createInstance(Type.resolveClass('ClientZeroArgToString'), []))); + } + @:depends(testStatus) function testInterfaceCalling() { final obj : IFoo = Type.createInstance(Type.resolveClass('ClientFoo'), []); diff --git a/test/cppia/compile-host.hxml b/test/cppia/compile-host.hxml index 76bf2d790..31e6c19e6 100644 --- a/test/cppia/compile-host.hxml +++ b/test/cppia/compile-host.hxml @@ -1,5 +1,6 @@ -m CppiaHost HostExtendedRoot +OptionalToString -D scriptable -D dll_export=host_classes.info -L utest