Make plugins visit the branches instead of the main If-node.
The plugins should return with a list of subjects for the given branch.
In the main analyzer, construct a dictionary of branch -> [ (plugin, [subjects]) ]
(A list of tuples saying: PluginXY can transform this branch using these subjects)
By going over and intersecting the returned subjects, determine if it can be transformed into a pattern match.
For example, given the code:
if isinstance(x, SomeClass) and x.prop = "something" and x.other_prop = 42 and something_else():
...
elif (x == None or x == "") and (y == "Error" or y ==404) and something_else():
...
For the first branch, LiteralCase would return nothing, while ClassCase would return a list: ["x"]
For the second branch, ClassCase would return nothing, while LiteralCase would return a list: ["x", "y"]
In the main analyzer this would look like:
branch1 -> [(ClassCase(), ["x"])]
branch2 -> [(LiteralCase(), ["x", "y"])]
After somehow making sense of the above, we can conclude that:
The whole If-node can be transformed into a pattern match, using the subject x.
Like so:
match x:
case SomeClass(prop = "something", other_prop=42) if something_else():
...
case None | "" if (y == "Error" or y == 404) and something_else():
...
Reworking the one (1!) already existing plugin to work this way shouldn't be too hard.
But implementing this in the main analyzer is going to be bit more difficult.
Some ideas:
After the dictionary is done, order it by the lenght of the lists.
Start intersecting - starting with the smallest list. This is the quickest way to make sure a node is not transformable.
After an intersection, if you are left with an empty set, try "going back", and try intersecting with the other tuples in the list.
If there are no other tuples, try "going back even more" to the previous branch, and selecting a new tuple.
If you cannot "go back" anymore, the node is not transformable.
If you are done with all the branches, and the intersection is not an empty list, the node can be transformed.
Select one of the subjects from the set (Could ask user?)
Go through all the branches again, this time delete all the tuples from the list, except one, that contains the selected subject.
In the end, you should end up with a dictionary: Branch -> [(Plugin(), [subject])] for every branch of the main If-node.
Even though this solution smells, in practice i dont think many (if any) branches are gonna have more than one plugin that can transform them.
Make plugins visit the branches instead of the main If-node. The plugins should return with a list of subjects for the given branch. In the main analyzer, construct a dictionary of branch -> [ (plugin, [subjects]) ] (A list of tuples saying: PluginXY can transform this branch using these subjects) By going over and intersecting the returned subjects, determine if it can be transformed into a pattern match. For example, given the code:
For the first branch, LiteralCase would return nothing, while ClassCase would return a list: ["x"] For the second branch, ClassCase would return nothing, while LiteralCase would return a list: ["x", "y"] In the main analyzer this would look like: branch1 -> [(ClassCase(), ["x"])] branch2 -> [(LiteralCase(), ["x", "y"])] After somehow making sense of the above, we can conclude that: The whole If-node can be transformed into a pattern match, using the subject x. Like so:
Reworking the one (1!) already existing plugin to work this way shouldn't be too hard. But implementing this in the main analyzer is going to be bit more difficult.
Even though this solution smells, in practice i dont think many (if any) branches are gonna have more than one plugin that can transform them.