TraitedMetaclass stores methods in two different instance variables:
- methodDict (inherited from Behavior)
- localMethods
And the same method can be different in each dictionary.
Yes, trais actually work by flattening the methods of traits into the user class.
- localMethods defines the methods actually defined in the class
- methodDict has the raw low-level flattened version the VM uses for the lookup
So methodDict and localMethods should always contain the same instanceof CompiledMethod for a given selector, right?
Yes it should.
We are tracking this, it seems you have found two bugs :D.
1st bug) Calypso and the senders/implementors should be in sync and show the same (always the local methods for traited classes)
From what you've written above (and below), the two dictionariesshould always have the same instance of CompiledMethod so it shouldn'tmatter where it comes from.
Just for the record, yes and no :).
With the new trait implementation, we have actually a modular MOP.
All classes (whether traited or not) understand:
- #localMethods => returning the methods as they are implemented in the class
- #methods => returning the actual methods executed by the VM
For normal classes, #localMethods is defined as follows:
localMethods
^ self methods
And methods understand
- #origin => where that method is actually defined
For example, in a traited class a lookup should be done in the trait composition, otherwise is the class where the method is installed.
Tools should work against #localMethods and #origin, which hide how the actual implementation stores the things :).
We already had in the past a lot of tools that hardcoded/duplicated the internals of Traits for example.
The other advantage of this is that, imagine you want to extend Pharo with:
- multimethods => localMethods will have a single multi-method implementation, and the implementation side will contain maybe an expanded version with type checks or even one method per type combination
- optional parameters => a single method with many parameters should be expanded to many methods with less parameters + default values
The idea is that tools do not hardcode the implementation details of these extensions, so the public API is #localMethods and #origin :)
Of course, one could design a tool showing #methods, which will show the low-level view of classes.
Moreover, (I���m confirming this with Pablo on line right now) the local methods dictionary should be just a subset of the method dictionary.
In other words, the invariant is that methods in localMethods should be included in the methoddict, they should not differ as they are differing there.
2nd bug) UFFI? seems to be messing with this invariant :)
When a UFFI binding is executed for the very first time, the ffi call definition gets compiled and a new compiled method is generated performing that call.
When a session finishes (the image stops/restarts) ffi bindings are reset (because their compilation is platform dependent).
What we are seeing there is that somehow UFFI managed to reset a binding method and put a wrong one in the local method
I���ve seen this bug before but never got the insight of what was producing it (neither put too much time to dig on it either :P).
Another possibility is that the culprit are the LibGit bindings, which extend UFFI binding compilation.
I haven't looked at this, but what I saw was the localMethods wascorrect and the methodDict was old:Redefine a base system method (LGitCommit>>commit_author:) using anextension method resulted in methodDict containing the originaldefinition and the localMethods containing the overridden method.What I would expect is that the overridden method is what is usedeverywhere.I'm not sure if this is related to UFFI or not.
Pablo tracked this down to possibly
FFIMethodRegistry >> removeMethod: aMethod
aMethod methodClass methodDict
at: aMethod selector
put: (aMethod propertyValueAt: #ffiNonCompiledMethod)