SymbolMatcher
Source:
SymbolMatcher.scala
Symbol matcher is a trait that is defined by a single method
matches(Symbol): Boolean
.
trait SymbolMatcher {
def matches(symbol: Symbol): Boolean
}
On top of matches()
, symbol matchers provide additional methods to extract
interesting tree nodes with pattern matching.
All code examples in this document assume you have the following imports in scope.
import scalafix.v1._
import scala.meta._
SemanticDB
Scalafix symbols are based on SemanticDB symbols. To learn more about SemanticDB symbols, consult the SemanticDB specification:
SymbolMatcher reference
There are two kinds of symbol matchers: exact and normalized. To illustrate the differences between exact and normalized symbol matchers, we use the following input source file as a running example.
// Main.scala
package com
class Main() {} // com/Main# class
object Main extends App { // com/Main. object
println() // println() overload with no parameter
println("Hello Predef!") // println(+1) overload with string parameter
}
Observe that the name
Main
can refer to both the class or the companion object.println
can refer to different method overloads.
exact()
Exact symbol matchers allow precise comparison between symbols with the ability to distinguish the differences between method overloads and a class from its companion object.
To match only the Main
class in the example above.
val mainClass = SymbolMatcher.exact("com/Main#")
// mainClass: SymbolMatcher = ExactSymbolMatcher(com/Main#)
mainClass.matches(Symbol("com/Main#"))
// res0: Boolean = true
mainClass.matches(Symbol("com/Main."))
// res1: Boolean = false
To match only the println(String)
method overload and not the println()
method.
val printlnString = SymbolMatcher.exact("scala/Predef.println(+1).")
// printlnString: SymbolMatcher = ExactSymbolMatcher(scala/Predef.println(+1).)
printlnString.matches(Symbol("scala/Predef.println()."))
// res2: Boolean = false
printlnString.matches(Symbol("scala/Predef.println(+1)."))
// res3: Boolean = true
normalized()
Normalized symbol matchers ignore the differences between overloaded methods and
a class from its companion object. The benefit of normalized symbols is that
they use a simple symbol syntax: fully qualified names with a dot .
separator.
For example,
java/lang/String#
is equal tojava.lang.String
normalizedscala/Predef.println(+1).
is equal toscala.Predef.println
normalized
To match against either the Main
class or companion object
val main = SymbolMatcher.normalized("com.Main")
// main: SymbolMatcher = NormalizedSymbolMatcher(com.Main.)
main.matches(Symbol("com/Main#")) // Main class
// res4: Boolean = true
main.matches(Symbol("com/Main.")) // Main object
// res5: Boolean = true
To match against all overloaded println
methods
val print = SymbolMatcher.normalized("scala.Predef.println")
// print: SymbolMatcher = NormalizedSymbolMatcher(scala.Predef.println.)
print.matches(Symbol("scala/Predef#println().")) // println()
// res6: Boolean = true
print.matches(Symbol("scala/Predef#println(+1).")) // println(String)
// res7: Boolean = true
unapply(Tree)
Use the unapply(Tree)
method to pattern match against tree nodes that resolve
to a specific symbol. Consider the following input file.
// Main.scala
object Main {
util.Success(1)
}
To match against the Scala util
package symbol
val utilPackage = SymbolMatcher.exact("scala/util/")
// utilPackage: SymbolMatcher = ExactSymbolMatcher(scala/util/)
doc.tree.traverse {
case utilPackage(name) =>
println(name.pos.formatMessage("info", "Here is the util package"))
}
// Main.scala:3:3: info: Here is the util package
// util.Success(1)
// ^^^^
A common mistake when using unapply(Tree)
is to match multiple tree nodes that
resolve to the same symbol.
val successObject = SymbolMatcher.exact("scala/util/Success.")
// successObject: SymbolMatcher = ExactSymbolMatcher(scala/util/Success.)
doc.tree.traverse {
case successObject(name) =>
println(name.pos.formatMessage("info",
"Matched Success for tree node " + name.productPrefix))
}
// Main.scala:3:3: info: Matched Success for tree node Term.Apply
// util.Success(1)
// ^^^^^^^^^^^^^^^
// Main.scala:3:3: info: Matched Success for tree node Term.Select
// util.Success(1)
// ^^^^^^^^^^^^
// Main.scala:3:8: info: Matched Success for tree node Term.Name
// util.Success(1)
// ^^^^^^^
To ensure we match the symbol only once, refine the pattern match to Name
.
doc.tree.traverse {
case successObject(name @ Name(_)) =>
println(name.pos.formatMessage("info", "Here is the Success name"))
}
// Main.scala:3:8: info: Here is the Success name
// util.Success(1)
// ^^^^^^^
Alternatively, enclose the symbol matcher guard inside a more complicated pattern.
doc.tree.traverse {
case function @ Term.Apply.After_4_6_0(successObject(_), Term.ArgClause(List(argument), _)) =>
println(function.pos.formatMessage("info",
s"Argument of Success is $argument"))
}
// Main.scala:3:3: info: Argument of Success is 1
// util.Success(1)
// ^^^^^^^^^^^^^^^
+(SymbolMatcher)
Use the +(SymbolMatcher)
method to combine two symbol matchers.
val printOrMain = main + print
The combined matcher returns true when either the main
or print
matchers
return true.
printOrMain.matches(Symbol("com/Main#"))
// res13: Boolean = true
printOrMain.matches(Symbol("scala/Predef.println()."))
// res14: Boolean = true