Monday, February 2, 2026

Deprecations as They Should Be

With BPatterns, you don’t need a special syntax to search or rewrite code. Any block or any AST node can become a matching pattern.

This opens up interesting opportunities to simplify existing users of the rewrite engine. One particularly good candidate is the deprecation mechanism in Pharo.

Let’s look at a real example.

Deprecations in Pharo Today

Consider this recently introduced deprecation in Object:

Object>>confirm: queryString
        self 
                deprecated: 'Use the ConfirmationRequest instead.' 
                transformWith: '`@rcv confirm: `@arg' -> 'ConfirmationRequest signal: `@arg'.
        ^ConfirmationRequest signal: queryString

This method will be removed in an upcoming Pharo version. Users are expected to migrate from #confirm: to raising a ConfirmationRequest.

The interesting part is the transformation rule: any sender of #confirm: is automatically rewritten on the fly using the provided rewrite pattern.

Run your test suite with good coverage, and your codebase is upgraded automatically. No manual work required. That’s powerful.

But Let’s Look Closer

Even in this relatively simple example, a lot is duplicated:

  • The deprecated message is duplicated twice in the transformation rule and the actual method name:
    • '`@rcv confirm: `@arg'
  • The replacement API appears three times:
    • in deprecation description: 
      • 'Use the ConfirmationRequest instead'
    • in the transformation rule: 
      • 'ConfirmationRequest signal: `@arg'
    • at the end of the method to do the real API call: 
      • ^ConfirmationRequest signal: queryString
  • The transformation rule duplicates symbolic pattern variables: 
    • `@arg 
If you are already familiar with Pharo’s deprecation API, this is manageable. But think about the first time you deprecated a method. How easy was it to write this from scratch?

Personally, I always start by searching for an existing example to copy. I never try to write it cold.

There is a “Deprecate Method” refactoring in Pharo, but today it is very basic and does not help with transformations. It could be improved—but that’s where we currently are.

Now let’s look at the same idea through the lens of BPatterns.

The Same Rewrite with BPatterns

The core transformation rule can be written as a plain block:

[[ anyRcv confirm: anyArg ] -> [ ConfirmationRequest signal: anyArg ]] brewrite.

No strings. No backticks. No special syntax.

Now imagine embedding this rewrite directly into the deprecated method:

Object>>confirm: queryString
        | brewrite anyRcv anyArg newAST |
        brewrite := [[ anyRcv confirm: anyArg ] -> [ ConfirmationRequest signal: anyArg ]] brewrite.

        "some code to actually rewrite the sender"
        newAST := brewrite rewriteMethod: thisContext sender home method.
        thisContext sender home receiver class compile: newAST formattedCode.

        "And call new API at the end"
        ^ConfirmationRequest signal: queryString

This already removes the string-based rewrite syntax, but we can do better.

In BPatterns, variables starting with any are wildcards by default—but you can explicitly configure any variable, including self and method arguments.

That means we can reuse the actual method variables directly in the rewrite rule:

Object>>confirm: queryString
        | brewrite newAST |
        brewrite := [[ self confirm: queryString ] -> [ ConfirmationRequest signal: queryString ]] 
                                        brewrite with: [ self. queryString ].

        "some code to actually rewrite the sender"
        newAST := brewrite rewriteMethod: thisContext sender home method.
        thisContext sender home receiver class compile: newAST formattedCode.

        "And call new API at the end"
        ^ConfirmationRequest signal: queryString

Now the transformation rule and the method body speak the same language. And we can easily refactor it like any other method to avoid the duplication of the new API call:

Object>>confirm: queryString
        | brewrite newAST |
        newAPI := [ ConfirmationRequest signal: queryString ].
        brewrite := [[ self confirm: queryString ] -> newAPI ] brewrite with: [ self. queryString ].

        "some code to actually rewrite the sender"
        newAST := brewrite rewriteMethod: thisContext sender home method.
        thisContext sender home receiver class compile: newAST formattedCode.

        ^ newAPI value

At this point, most of the code above is mechanical. BPatterns only needs an AST of the deprecated API call for the matching pattern. 

Normally it comes from a block - [ self confirm: queryString ] but here it can be easily derived from thisContext method ast since it is equivalent to the deprecated method header. 

That means the whole deprecation can collapse into this: 

Object>>confirm: queryString
        ^ self deprecatedBy:  [ ConfirmationRequest signal: queryString ]

That’s it.

No duplicated selectors.
No duplicated replacement calls.
No rewrite strings.
No boilerplate.

This is the deprecation without stress.

Variations Are Easy

Once the transformation is automatic, it’s natural to allow variations:

Disable auto-transformation:

Object>>confirm: queryString
        ^self 
                deprecatedBy: [ ConfirmationRequest signal: queryString ] 
                autoTransform: false

Add a custom deprecation message:

Object>>confirm: queryString
        ^ self 
                deprecated: 'self confirm: is a bad style , use ConfirmationRequest'
                by:  [ ConfirmationRequest signal: queryString ] 

The basic version generates a default title automatically.

Summary

With the new API, the deprecation becomes a seamless, fluent process. You no longer need to think about transformation rules at all—they are simply part of the system, enabled by default. Deprecation is expressed in the same language as the code itself, not in a separate, string-based mini-language.

Once this foundation is in place, building higher-level tooling becomes straightforward. Wrapping deprecations into proper refactoring commands is trivial, and integrating them into existing refactorings—such as Rename Method or Add/Remove Argument—becomes a natural next step.

That’s all for now.

You can load BPatterns from GitHub and try new deprecations yourself:

👉 https://github.com/dionisiydk/BPatterns

BPatterns: Rewrite Engine with Smalltalk style

The rewrite engine is an absolutely brilliant invention by John Brant and Don Roberts, introduced with the Refactoring Browser (see “A Refactoring Tool for Smalltalk”, 1997). It gives us AST-level matching and rewriting with astonishing power.

But let’s be honest: how many people actually remember its syntax?

Even the simplest rewrite rule—say, replacing a deprecated message with a new one—usually sends me hunting for examples. During this project I spent a lot of time deep inside the rewrite engine, and even now I cannot reliably recall the exact syntax.

Is it something like this?

``@receiver isNil ifTrue: ``@nilBlock -> ``@receiver ifNil: ``@nilBlock

Or maybe with single backticks?

`@receiver isNil ifTrue: `@nilBlock -> `@receiver ifNil: `@nilBlock

In fact, both versions work—but they apply different filters to the target node. Try to remember which one.

And that’s only the beginning. 

Do you know you can wildcard parts of selectors?

`@receiver `anyKeywordPart: `@arg1 staticPart: `@arg2

You can rename keywords using it:

`@receiver newKeywordPart: `@arg1 staticPart: `@arg2

Or even swap them:

`@receiver staticPart: `@arg2 `anyKeywordPart: `@arg1

It’s incredibly powerful. But how do you remember all of this?

With normal Smalltalk code, I would explore the system using senders, implementors, inspectors— gradually rebuilding my understanding. Here, that breaks down. The matching syntax lives inside strings, invisible to standard navigation tools. No code completion. No refactorings. No help from the environment.

So how do we keep the power without the syntax tax?

That is where BPatterns come in:

[ any isNil ifTrue: anyBlock ] bpattern

BPatterns

BPatterns provide a fluent, Smalltalk-native API on top of the rewrite engine, using ordinary Smalltalk blocks as patterns.

You create a BPattern instance by sending the #bpattern message to a block. The variables and selectors inside the block define the pattern to be matched against target AST nodes. By convention anything started with any word acts as a wildcard. Everything else must match structurally.

Under the hood, BPattern builds a pattern AST using the same pattern node classes as the rewrite engine. All the original matching and rewriting machinery is still there — just wrapped in a more approachable, scriptable interface.

You can think of BPatterns as a Smalltalk DSL for the rewrite engine.

Pharo already provides dedicated tools for the rewrite engine, such as StRewriterMatchToolPresenter:


Glamorous Toolkit adds its own powerful helpers.

With BPatterns, none of that is required. A pattern is just a block. Add one more message and simple DoIt will do the job.

To find all matching methods:
[ anyRcv isNil ifTrue: anyBlock ] bpattern browseUsers

To rewrite them:

[[ anyRcv isNil ifTrue: anyBlock ] ->  [ anyRcv ifNil: anyBlock ]] brewrite preview

 

Refining Patterns Explicitly


You can narrow patterns explicitly using #with: message:

[ anyVar isNil ifTrue: anyBlock ] bpattern with: [ anyVar ] -> [:pattern | pattern beVariable ]

Because this is regular Smalltalk code, all standard development tools work out of the box: syntax highlighting, code completion, navigation, and refactorings:


Browse the implementors of #beVariable message and you will find other filters under BPatternVariableNode class, such as #beInstVar or #beLocalVar. If you miss something, just add a method. No new syntax required.

You can also use an arbitrary block as a filter: 

[ anyVar isNil ifTrue: anyBlock ] bpattern 

         with: [ anyVar ] -> [:pattern | 

                           pattern beInstVar where: [:var | var name beginsWith: 'somePrefix' ]]

Notice the block [anyVar] is used to reference variables where the configuration block should be applied. This avoids raw strings for variable names and keeps these configs friendly to development tools:



Message Patterns Revisited


Now let’s revisit the selector wildcard examples from the beginning using BPatterns.


Renaming a keyword:

[

         anyRcv anyKeywordPart: anyArg1 staticPart: anyArg2 ] 

                  -> [ anyRcv newKeywordPart: anyArg1 staticPart: anyArg2 ]

] brewrite.

Swapping keywords:

[

         anyRcv anyKeywordPart: anyArg1 staticPart: anyArg2 ] 

                  -> [ anyRcv staticPart: anyArg2 anyKeywordPart: anyArg1 ]

] brewrite.

Message patterns can also be refined using #with: message:

[ any anyMessage: any2 ] bpattern 

with: #anyMessage: -> [:pattern | pattern beBinary ];

browseUsers.

This finds all methods containing binary messages:


Add another filter to keep only binaries between literals:

[ any anyMessage: any2 ] bpattern 

with: #anyMessage: -> [:pattern | pattern beBinary ];

with: [ any. any2 ] -> [ :pattern | pattern beLiteral ]; 

browseUsers


The old syntax also supports literal patterns but good luck finding an example.

Message patterns can also be configured with arbitrary conditions:

[ any anyMessage ] bpattern 

with: #anyMessage -> [:pattern | pattern where: [:node | node selector beginsWith: 'prim' ]];

browseUsers


Status and What’s Next

BPatterns don’t expose every feature of the rewrite engine yet, but many are already supported, including full method patterns via #bmethod.

For full details, see the GitHub repository:

And check the next blog post about a simplified deprecation API built on top of BPatterns:

Tuesday, January 9, 2018

Calypso in progress 0.8.7

It is important release which prepares Calypso for new TelePharo version and removes the main Nautilus dependency.

Now all Nautilus method icons are done in Calypso way with extra features:
  • override/overridden method icons (up/down arrows).
Up/down buttons open method browser scoped to superclasses or subclasses.
Method context menu "Inheritance" with shortcut cmd+h.
  • icon for methods marked with #flag: message 
In addition new method group "flags" is implemented. 


  • icon for ffi methods 
In addition new method group "ffi calls" is implemented. 


  • test icon for methods covered by tests 


The implementation brings additional feature to run inherited tests when they are visible in the browser.



And you can run tests when abstract test case is selected. In that case tests are executed for all concrete subclasses.
  • extra "expected failures" method group for tests

  • when browser shows class side the title is bold


  • open dependency browser command for packages
  • commit package command uses Iceberg. cmd+s to open Iceberg Sync tool. 
  • decoration button for "seeClassSide" protocol and method. Button switches browser to the class side.



Tuesday, December 12, 2017

New Calypso version 0.8 is out

Finally I finish major Calypso changes. It is a big refactoring on navigation model and browser pluggability.

For the model part the biggest change is composition of queries and scopes. 
For example string lookup in class comments and method sources looks like:

(ClyClassComments withSubstring: 'test') , (ClyMethodSources withSubstring: 'test')

Query of all variables accessible from the class can be done with:

ClyAllVariables fromAll: {ClyClassScope. ClySuperclassScope} of: Morph in: environment

In the browser these compositions are created from some concrete scope using conversion methods. For example inherited scope can be constructed with:

classScope , (classScope asScope: ClySuperclassScope)

And full hierarchy scope looks like:

classScope
     , (classScope asScope: ClySuperclassScope)
     , (classScope asScope: ClySubclassScope)

The next important feature is async queries. Now it is possible to evaluate any query in background. Just convert given query using #async message:

asyncQuery := aQuery async

Browser provides nice visualisation for execution process. It is very simple rotating icon. But real progress indication can be implemented later. Try following script to see it in action:

ClyQueryBrowser openOn: (
    (ClyClassComments withString: 'test'), (ClyMethodSources withString: 'example')) async.

Composition and async queries were required to support advanced search functions like "method source with it". Also critic plugin is now based on them instead of complicated internal implementation for background analysis. 
Nice detail about async queries: they are not bound to global progress bar. The animation is shown in the browser and the query not blocks UI.

With help of new composed design I was able to redo all method groups using queries. Normally if you want new method group you do not need new subclass of group. You only implement new query and group provider instantiates a group on it. For example breakpoints provider is implemented with following methods:

ClyBreakMethodGroupProvider>>createMethodQueryFrom: aClassScope
^ClyActiveBreakpoints from: aClassScope

ClyBreakMethodGroupProvider>>createMethodGroupFor: aMethodQuery from: aClassScope
^ClyMethodGroup named: 'breakpoints' priority: 8.4 on: aMethodQuery 

Interesting that in case when provider returns async query it will be automatically represented by group item with progress animation. It is how critic method group is now implemented. 

Another big change is the result of query. If you will execute query it will return the instance of ClyQueryResult subclass. Previously it was always cursor object which is now opened by demand from given result. 
Purpose of different kinds of result is to format items in different forms. There is ClySortedQueryResult with sort function as parameter (that's why I worked on SortFunction). And there are many kinds of result which return hierarchical browser items.
You can get any query with required result:

newQuery := classQuery withResult: (ClySortedQueryResult using: #name ascending)
newQuery := classQuery withResult: ClySubclassHierarchy new asQueryResult 

By default any query is created with ClyRawQueryResult which represents retrieved items without any formatting. So by default any query returns raw methods, classes and packages. It allows in future to replace current SystemNavigation with Calypso query model.

On the browser part there are many renames. As you noticed ClyQueryBrowser replaced method browser with ability to show mixed query result. In example it shows class comments and methods together in single list. It also able to show classes. Interesting that hierarchical view and sorted view modes are working in all these cases.
The ClySystemBrowser is renamed to ClyFullBrowser.

There are a lot of other internal changes and bug fixes. Here is short list of them:
  • better class hierarchies implementation
    • merged hierarchies
      • mixed traits and subclasses/superclasses relation
    • inverse relation hierarchy
      • pluggable order for variables view
      • pluggable order for class visibility view
  • class annotations for browser pluggability
    • table decorators are annotated by browser context where they are applied
    • browser tools are annotated by browser context where they are activated as tabs
  • new Commander based on class annotations
  • repackaged code
  • many classes are renamed
  • traits are in separate plugin
  • browser tabs subscribe on system changes by themselves. It fixes sync bug when tabs are not updated after system changes
  • search packages like in Nautilus with default persistent filter
  • a lot of new system queries
Now I start document new design and I will post more details soon