From cf3d3c460ad7231c118c2c42b6a07ada50375c8e Mon Sep 17 00:00:00 2001 From: Yavor Georgiev Date: Fri, 31 Jul 2015 14:39:49 +0300 Subject: [PATCH] Fix TypeScript inheritance of Objective-C classes There are more places the original constructor function resides in various circumstances. We loop over all the frame registers AND the lexical environment of the scope chain to try and replace the constructor function with our own. --- .../ObjC/Inheritance/ObjCTypeScriptExtend.mm | 15 ++- .../app/Inheritance/TypeScriptTests.js | 115 +++++++----------- .../app/Inheritance/TypeScriptTests.ts | 14 +++ 3 files changed, 69 insertions(+), 75 deletions(-) diff --git a/src/NativeScript/ObjC/Inheritance/ObjCTypeScriptExtend.mm b/src/NativeScript/ObjC/Inheritance/ObjCTypeScriptExtend.mm index deac8dd2d..fc8e1412f 100644 --- a/src/NativeScript/ObjC/Inheritance/ObjCTypeScriptExtend.mm +++ b/src/NativeScript/ObjC/Inheritance/ObjCTypeScriptExtend.mm @@ -70,8 +70,19 @@ EncodedJSValue ObjCTypeScriptExtendFunction(ExecState* execState) { } CallFrame* callFrame = execState->callerFrame(); - // Replace the TypeScript constructor with ours - see Interpreter::dumpRegisters - callFrame->r(-3) = derivedConstructor; + for (Register* r = callFrame->registers(); r > callFrame->topOfFrame(); r--) { + if (r->unboxedCell() == typeScriptConstructor) { + *r = derivedConstructor; + } + } + + JSScope* scope = callFrame->scope(callFrame->codeBlock()->scopeRegister().offset()); + Identifier constructorName = Identifier::fromString(execState, name); + JSValue value = JSScope::resolve(execState, scope, constructorName); + if (value.isObject()) { + PutPropertySlot slot(value); + value.put(execState, constructorName, derivedConstructor, slot); + } // imp_implementationWithBlock calls block copy, class copy and initialize gets skipped __block Class derivedClass = derivedConstructor->klass(); diff --git a/tests/TestRunner/app/Inheritance/TypeScriptTests.js b/tests/TestRunner/app/Inheritance/TypeScriptTests.js index 49aba602f..a9f219719 100644 --- a/tests/TestRunner/app/Inheritance/TypeScriptTests.js +++ b/tests/TestRunner/app/Inheritance/TypeScriptTests.js @@ -1,42 +1,33 @@ // TODO: Use TypeScript definitions when they get ready -var __extends = this.__extends || function (d, b) { - for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; - function __() { - this.constructor = d; - } - - __.prototype = b.prototype; - d.prototype = new __(); - }; - +var __extends = (this && this.__extends) || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + __.prototype = b.prototype; + d.prototype = new __(); +}; var TSObject = (function (_super) { __extends(TSObject, _super); function TSObject() { _super.apply(this, arguments); } - TSObject.prototype.initBaseMethod = function () { var self = _super.prototype.initBaseMethod.call(this); TNSLog('js initBaseMethod called'); return self; }; - TSObject.prototype.initDerivedMethod = function () { var self = _super.prototype.initDerivedMethod.call(this); TNSLog('js initDerivedMethod called'); return self; }; - TSObject.prototype.baseMethod = function () { TNSLog('js baseMethod called'); _super.prototype.baseMethod.call(this); }; - TSObject.prototype.derivedMethod = function () { TNSLog('js derivedMethod called'); _super.prototype.derivedMethod.call(this); }; - Object.defineProperty(TSObject.prototype, "baseProperty", { get: function () { TNSLog('js getBaseProperty called'); @@ -49,8 +40,6 @@ var TSObject = (function (_super) { enumerable: true, configurable: true }); - - Object.defineProperty(TSObject.prototype, "derivedProperty", { get: function () { TNSLog('js getDerivedProperty called'); @@ -63,43 +52,37 @@ var TSObject = (function (_super) { enumerable: true, configurable: true }); - - TSObject.prototype.method = function () { return this.constructor.property; }; - TSObject.prototype.voidSelector = function () { TNSLog('voidSelector called'); }; - TSObject.prototype['variadicSelector:x:'] = function (a, b) { TNSLog('variadicSelector:' + a + ' x:' + b + ' called'); return a; }; + TSObject.returnsConstructorMethod = function () { + return TSObject; + }; TSObject.property = 1; - TSObject.ObjCExposedMethods = { - 'voidSelector': {returns: interop.types.void}, - 'variadicSelector:x:': {returns: NSObject, params: [NSString, interop.types.int32]} + 'voidSelector': { returns: interop.types.void }, + 'variadicSelector:x:': { returns: NSObject, params: [NSString, interop.types.int32] } }; return TSObject; })(TNSDerivedInterface); - var TSObject1 = (function (_super) { __extends(TSObject1, _super); function TSObject1() { _super.apply(this, arguments); } - TSObject1.prototype.baseProtocolMethod1 = function () { TNSLog('baseProtocolMethod1 called'); }; - TSObject1.prototype.baseProtocolMethod2 = function () { TNSLog('baseProtocolMethod2 called'); }; - Object.defineProperty(TSObject1.prototype, "baseProtocolProperty1", { get: function () { TNSLog('baseProtocolProperty1 called'); @@ -111,8 +94,6 @@ var TSObject1 = (function (_super) { enumerable: true, configurable: true }); - - Object.defineProperty(TSObject1.prototype, "baseProtocolProperty1Optional", { get: function () { TNSLog('baseProtocolProperty1Optional called'); @@ -124,125 +105,113 @@ var TSObject1 = (function (_super) { enumerable: true, configurable: true }); - - TSObject1.ObjCProtocols = [TNSBaseProtocol2]; return TSObject1; })(NSObject); - var A = (function () { function A() { } - return A; })(); - var B = (function (_super) { __extends(B, _super); function B() { _super.apply(this, arguments); } - return B; })(A); - var UnusedConstructor = (function (_super) { __extends(UnusedConstructor, _super); function UnusedConstructor() { _super.apply(this, arguments); this.x = 3; } - return UnusedConstructor; })(NSObject); - describe(module.id, function () { afterEach(function () { TNSClearOutput(); }); - + it('should replace the TypeScript-generated constructor function', function () { + expect(interop.handleof(TSObject)).toEqual(jasmine.any(interop.Pointer)); + expect(NSClassFromString(TSObject.name)).toBe(TSObject); + expect(TSObject.returnsConstructorMethod()).toBe(TSObject); + }); it('SimpleInheritance', function () { var object = TSObject.alloc().init(); expect(object.constructor).toBe(TSObject); - expect(object instanceof TSObject).toBe(true); expect(object instanceof TNSDerivedInterface).toBe(true); expect(object instanceof NSObject).toBe(true); - expect(object.class()).toBe(TSObject); expect(object.superclass).toBe(TNSDerivedInterface); - expect(TSObject.class()).toBe(TSObject); expect(TSObject.superclass()).toBe(TNSDerivedInterface); - expect(NSStringFromClass(TSObject)).toBe('TSObject'); }); - it('StaticMethods', function () { TSObject.baseMethod(); TSObject.derivedMethod(); - expect(TNSGetOutput()).toBe('static baseMethod called' + 'static derivedMethod called'); + expect(TNSGetOutput()).toBe('static baseMethod called' + + 'static derivedMethod called'); }); - it('InstanceMethods', function () { var object = TSObject.alloc().init(); - object.baseMethod(); object.derivedMethod(); - - expect(TNSGetOutput()).toBe('js baseMethod called' + 'instance baseMethod called' + 'js derivedMethod called' + 'instance derivedMethod called'); + expect(TNSGetOutput()).toBe('js baseMethod called' + + 'instance baseMethod called' + + 'js derivedMethod called' + + 'instance derivedMethod called'); }); - it('ConstructorCalls', function () { expect(TSObject.alloc().initBaseMethod() instanceof TSObject).toBe(true); expect(TSObject.alloc().initDerivedMethod() instanceof TSObject).toBe(true); - - expect(TNSGetOutput()).toBe('constructor initBaseMethod called' + 'js initBaseMethod called' + 'constructor initDerivedMethod called' + 'js initDerivedMethod called'); + expect(TNSGetOutput()).toBe('constructor initBaseMethod called' + + 'js initBaseMethod called' + + 'constructor initDerivedMethod called' + + 'js initDerivedMethod called'); }); - it('PropertyCalls', function () { var object = TSObject.alloc().init(); - object.baseProperty = 0; UNUSED(object.baseProperty); - object.derivedProperty = 0; UNUSED(object.derivedProperty); - - expect(TNSGetOutput()).toBe('js setBaseProperty called' + 'instance setBaseProperty: called' + 'js getBaseProperty called' + 'instance baseProperty called' + 'js setDerivedProperty called' + 'instance setDerivedProperty: called' + 'js getDerivedProperty called' + 'instance derivedProperty called'); + expect(TNSGetOutput()).toBe('js setBaseProperty called' + + 'instance setBaseProperty: called' + + 'js getBaseProperty called' + + 'instance baseProperty called' + + 'js setDerivedProperty called' + + 'instance setDerivedProperty: called' + + 'js getDerivedProperty called' + + 'instance derivedProperty called'); }); - it('ExposedMethods', function () { var object = TSObject.alloc().init(); - TNSTestNativeCallbacks.inheritanceVoidSelector(object); expect(TNSTestNativeCallbacks.inheritanceVariadicSelector(object)).toBe('native'); - - expect(TNSGetOutput()).toBe('voidSelector called' + 'variadicSelector:native x:9 called'); + expect(TNSGetOutput()).toBe('voidSelector called' + + 'variadicSelector:native x:9 called'); }); - it('AddedNewProperty', function () { var object = TSObject.alloc().init(); - expect(object.method()).toBe(1); }); - it('ImplementMethod', function () { var object = TSObject1.alloc().init(); - TNSTestNativeCallbacks.protocolImplementationProtocolInheritance(object); - - expect(TNSGetOutput()).toBe('baseProtocolMethod1 called' + 'baseProtocolMethod2 called'); + expect(TNSGetOutput()).toBe('baseProtocolMethod1 called' + + 'baseProtocolMethod2 called'); }); - it('ImplementProperties', function () { var object = TSObject1.alloc().init(); - TNSTestNativeCallbacks.protocolImplementationProperties(object); - - expect(TNSGetOutput()).toBe('setBaseProtocolProperty1: called' + 'baseProtocolProperty1 called' + 'setBaseProtocolProperty1Optional: called' + 'baseProtocolProperty1Optional called'); + expect(TNSGetOutput()).toBe('setBaseProtocolProperty1: called' + + 'baseProtocolProperty1 called' + + 'setBaseProtocolProperty1Optional: called' + + 'baseProtocolProperty1Optional called'); }); - it('PlainExtends', function () { expect(new B() instanceof A).toBe(true); }); diff --git a/tests/TestRunner/app/Inheritance/TypeScriptTests.ts b/tests/TestRunner/app/Inheritance/TypeScriptTests.ts index 5ad49cb2f..ba74bbbe6 100644 --- a/tests/TestRunner/app/Inheritance/TypeScriptTests.ts +++ b/tests/TestRunner/app/Inheritance/TypeScriptTests.ts @@ -8,6 +8,10 @@ declare function UNUSED(param); declare var module; declare function NSStringFromClass(klass); +declare function NSClassFromString(klassName); + +declare var interop; +declare var jasmine; declare class NSObject { public static alloc(); @@ -105,6 +109,10 @@ class TSObject extends TNSDerivedInterface { return a; } + public static returnsConstructorMethod() { + return TSObject; + } + public static ObjCExposedMethods = { 'voidSelector': { returns: interop.types.void }, 'variadicSelector:x:': { returns: NSObject, params: [ NSString, interop.types.int32 ] } @@ -158,6 +166,12 @@ describe(module.id, function () { TNSClearOutput(); }); + it('should replace the TypeScript-generated constructor function', function () { + expect(interop.handleof(TSObject)).toEqual(jasmine.any(interop.Pointer)); + expect(NSClassFromString(TSObject.name)).toBe(TSObject); + expect(TSObject.returnsConstructorMethod()).toBe(TSObject); + }); + it('SimpleInheritance', function () { var object = TSObject.alloc().init(); expect(object.constructor).toBe(TSObject);