Search code examples
reflectionpharo

Conditionally skip a method with MetaLinks


Imagine we have some method

MyClass>>#method: arg

    Transcript crShow: 'executed'

So when you do MyClass new method: 1 the transcript is filled with "executed" lines.

Now I want to skip this method if arg is 0. I've tried to install an instead metalink with a condition:

link := MetaLink new
   condition: [ :arguments |
      arguments first = 0 ]
   arguments: #(arguments);
   control: #instead.

(MyClass >> #method:) ast link: link

But then the method does not run anymore and I want to run it if the arg is not 0.

I've also tried to do the condition in the metaobject in this way:

link := MetaLink new
   metaObject: [ :ast :arguments :receiver | 
      arguments first = 0
      ifFalse: [
         ast compiledMethod
            valueWithReceiver: receiver
            arguments: arguments ] ];
   selector: #value:value:value:;
   arguments: #(node arguments receiver);
   control: #instead.

(MyClass >> #method:) ast link: link

But in this case you end up in a infinite recursion, as the metalink is called over and over again although I thought that ast compiledMethod should return a compiled method and not the reflective counterpart


Solution

  • yes, it looks like "instead hooks" are always executed "instead" of the original method, even if the link condition does not hold, the difference is just whether we return the value of the instead link evaluation or just nil.

    Maybe this should be changed for instead links.

    As solution for your usecase, you can use a before link that just returns the receiver if the condition holds:

    | ml |
    ml := MetaLink new.
    ml control: #before.
    ml condition:[:args | args first = 0] arguments:#(arguments).
    ml selector:#value:.
    ml metaObject:[:context | context return].
    ml arguments:{#context}.
    
    (MyObject>>#method:) ast link:ml.
    

    the #context is the key for the thisContext refication (RFThisContextReification)