Browse Source

kernel: .methods form an inheritance chain

Also change implementations of Behavior >> lookupSelector:
and Behavior >> canUnderstand: to use the shorter way.
Herby Vojčík 4 years ago
parent
commit
13485da539

+ 18 - 9
lang/base/kernel-fundamentals.js

@@ -8,6 +8,8 @@ define(['./junk-drawer'], function ($goodies) {
     var addElement = $goodies.addElement;
     var removeElement = $goodies.removeElement;
 
+    var hop = Object.prototype.hasOwnProperty;
+
     function SelectorsBrik (brikz, st) {
         var selectorSet = Object.create(null);
         var selectors = this.selectors = [];
@@ -171,7 +173,8 @@ define(['./junk-drawer'], function ($goodies) {
 
             this.setupMethods = function (traitOrBehavior) {
                 traitOrBehavior.localMethods = Object.create(null);
-                traitOrBehavior.methods = Object.create(null);
+                var superclass = traitOrBehavior.superclass;
+                traitOrBehavior.methods = Object.create(superclass ? superclass.methods : null);
             };
 
             function setLocalMethods (traitOrBehavior, newLocalMethods) {
@@ -191,13 +194,19 @@ define(['./junk-drawer'], function ($goodies) {
             declareEvent("methodReplaced");
 
             function updateMethod (selector, traitOrBehavior) {
-                var oldMethod = traitOrBehavior.methods[selector],
-                    newMethod = traitOrBehavior.localMethods[selector];
-                if (oldMethod == null && newMethod == null) {
-                    console.warn("Removal of nonexistent method " + traitOrBehavior + " >> " + selector);
-                    return;
+                var oldMethod,
+                    newMethod = traitOrBehavior.localMethods[selector],
+                    methods = traitOrBehavior.methods;
+                if (hop.call(methods, selector)) {
+                    oldMethod = methods[selector];
+                    if (newMethod === oldMethod) return;
+                } else {
+                    if (newMethod == null) {
+                        console.warn("Removal of nonexistent method " + traitOrBehavior + " >> " + selector);
+                        return;
+                    }
+                    oldMethod = null;
                 }
-                if (newMethod === oldMethod) return;
                 if (newMethod != null) {
                     if (newMethod.methodClass && newMethod.methodClass !== traitOrBehavior) {
                         console.warn("Resetting methodClass of " + newMethod.methodClass.name + " >> " + selector + " to " + traitOrBehavior.name);
@@ -206,10 +215,10 @@ define(['./junk-drawer'], function ($goodies) {
                     if (newMethod.instantiateFn) {
                         newMethod.fn = newMethod.instantiateFn(traitOrBehavior);
                     }
-                    traitOrBehavior.methods[selector] = newMethod;
+                    methods[selector] = newMethod;
                     traitOrBehavior.methodAdded(newMethod);
                 } else {
-                    delete traitOrBehavior.methods[selector];
+                    delete methods[selector];
                     traitOrBehavior.methodRemoved(oldMethod);
                 }
                 emit.methodReplaced(newMethod, oldMethod, traitOrBehavior);

+ 2 - 1
lang/base/kernel-language.js

@@ -243,13 +243,14 @@ define(['./junk-drawer'], function ($goodies) {
             var nilAsClass = this.nilAsClass = {
                 fn: SmalltalkRoot,
                 subclasses: [],
-                a$cls: {fn: SmalltalkClass}
+                a$cls: {fn: SmalltalkClass, methods: Object.create(null)}
             };
 
             this.bootstrapHierarchy = function (realClass) {
                 nilAsClass.a$cls = realClass;
                 nilAsClass.subclasses.forEach(function (each) {
                     each.a$cls.superclass = realClass;
+                    Object.setPrototypeOf(each.a$cls.methods, realClass.methods);
                     registerToSuperclass(each.a$cls);
                 });
             };

+ 3 - 1
lang/base/kernel-runtime.js

@@ -10,6 +10,8 @@ define(['./junk-drawer'], function ($goodies) {
     var deleteKeysFrom = $goodies.deleteKeysFrom;
     var extendWithMethods = $goodies.extendWithMethods;
 
+    var hop = Object.prototype.hasOwnProperty;
+
     function cleanMethodOfJsObjectEx (obj, name) {
         var attachments;
         var old = Object.getOwnPropertyDescriptor(obj, name);
@@ -271,7 +273,7 @@ define(['./junk-drawer'], function ($goodies) {
                 var jsSelector = method.jsSelector;
                 st.traverseClassTree(klass, function (subclass, sentinel) {
                     if (subclass === exclude) return;
-                    if (subclass.methods[selector]) return sentinel;
+                    if (hop.call(subclass.methods, selector)) return sentinel;
                     if (subclass.detachedRoot) {
                         installMethodOfJsObjectEx(subclass.fn.prototype, jsSelector, subclass.superclass.fn.prototype[jsSelector]);
                     }

+ 9 - 47
lang/src/Kernel-Classes.js

@@ -324,29 +324,17 @@ selector: "canUnderstand:",
 protocol: "testing",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["aSelector"],
-source: "canUnderstand: aSelector\x0a\x09^ (self includesSelector: aSelector asString) or: [\x0a\x09\x09self superclass ifNil: [ false ] ifNotNil: [ :superClass | superClass canUnderstand: aSelector ]]",
+source: "canUnderstand: aSelector\x0a\x09^ (self lookupSelector: aSelector) notNil",
 referencedClasses: [],
 //>>excludeEnd("ide");
 pragmas: [],
-messageSends: ["or:", "includesSelector:", "asString", "ifNil:ifNotNil:", "superclass", "canUnderstand:"]
+messageSends: ["notNil", "lookupSelector:"]
 }, function ($methodClass){ return function (aSelector){
 var self=this,$self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-var $1;
-if($core.assert($self._includesSelector_($recv(aSelector)._asString()))){
-return true;
-} else {
-$1=$self._superclass();
-if($1 == null || $1.a$nil){
-return false;
-} else {
-var superClass;
-superClass=$1;
-return $recv(superClass)._canUnderstand_(aSelector);
-}
-}
+return $recv($self._lookupSelector_(aSelector))._notNil();
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 }, function($ctx1) {$ctx1.fill(self,"canUnderstand:",{aSelector:aSelector})});
 //>>excludeEnd("ctx");
@@ -543,46 +531,20 @@ selector: "lookupSelector:",
 protocol: "accessing",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["selector"],
-source: "lookupSelector: selector\x0a\x09\x22Look up the given selector in my methodDictionary.\x0a\x09Return the corresponding method if found.\x0a\x09Otherwise chase the superclass chain and try again.\x0a\x09Return nil if no method is found.\x22\x0a\x09\x0a\x09| lookupClass |\x0a\x09\x0a\x09lookupClass := self.\x0a\x09[ lookupClass = nil ] whileFalse: [\x0a\x09\x09(lookupClass includesSelector: selector)\x0a\x09\x09\x09\x09ifTrue: [ ^ lookupClass methodAt: selector ].\x0a\x09\x09\x09lookupClass := lookupClass superclass ].\x0a\x09^ nil",
+source: "lookupSelector: selector\x0a\x09\x22Look up the given selector in my methodDictionary.\x0a\x09Return the corresponding method if found.\x0a\x09Otherwise chase the superclass chain and try again.\x0a\x09Return nil if no method is found.\x22\x0a\x09\x0a\x09<inlineJS: 'return $self.methods[selector]'>",
 referencedClasses: [],
 //>>excludeEnd("ide");
-pragmas: [],
-messageSends: ["whileFalse:", "=", "ifTrue:", "includesSelector:", "methodAt:", "superclass"]
+pragmas: [["inlineJS:", ["return $self.methods[selector]"]]],
+messageSends: []
 }, function ($methodClass){ return function (selector){
 var self=this,$self=this;
-var lookupClass;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-var $early={};
-try {
-lookupClass=self;
-$recv((function(){
-//>>excludeStart("ctx", pragmas.excludeDebugContexts);
-return $core.withContext(function($ctx2) {
-//>>excludeEnd("ctx");
-return $recv(lookupClass).__eq(nil);
-//>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx2) {$ctx2.fillBlock({},$ctx1,1)});
-//>>excludeEnd("ctx");
-}))._whileFalse_((function(){
-//>>excludeStart("ctx", pragmas.excludeDebugContexts);
-return $core.withContext(function($ctx2) {
-//>>excludeEnd("ctx");
-if($core.assert($recv(lookupClass)._includesSelector_(selector))){
-throw $early=[$recv(lookupClass)._methodAt_(selector)];
-}
-lookupClass=$recv(lookupClass)._superclass();
-return lookupClass;
-//>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx2) {$ctx2.fillBlock({},$ctx1,2)});
-//>>excludeEnd("ctx");
-}));
-return nil;
-}
-catch(e) {if(e===$early)return e[0]; throw e}
+return $self.methods[selector];
+return self;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx1) {$ctx1.fill(self,"lookupSelector:",{selector:selector,lookupClass:lookupClass})});
+}, function($ctx1) {$ctx1.fill(self,"lookupSelector:",{selector:selector})});
 //>>excludeEnd("ctx");
 }; }),
 $globals.Behavior);

+ 2 - 10
lang/src/Kernel-Classes.st

@@ -99,14 +99,7 @@ lookupSelector: selector
 	Otherwise chase the superclass chain and try again.
 	Return nil if no method is found."
 	
-	| lookupClass |
-	
-	lookupClass := self.
-	[ lookupClass = nil ] whileFalse: [
-		(lookupClass includesSelector: selector)
-				ifTrue: [ ^ lookupClass methodAt: selector ].
-			lookupClass := lookupClass superclass ].
-	^ nil
+	<inlineJS: 'return $self.methods[selector]'>
 !
 
 prototype
@@ -174,8 +167,7 @@ makeJavaScriptConstructorSubclassOf: javaScriptClass
 !Behavior methodsFor: 'testing'!
 
 canUnderstand: aSelector
-	^ (self includesSelector: aSelector asString) or: [
-		self superclass ifNil: [ false ] ifNotNil: [ :superClass | superClass canUnderstand: aSelector ]]
+	^ (self lookupSelector: aSelector) notNil
 !
 
 includesBehavior: aClass