Scalafix

Scalafix

  • User guide
  • Developer guide
  • Browse sources
  • GitHub

›API Reference

Implementing rules

  • Setup
  • Before you write code
  • Tutorial

API Reference

  • Overview
  • Patch
  • SymbolMatcher
  • SymbolInformation
  • SemanticType
  • SemanticTree

Contributing

  • Guide
Edit

SemanticTree

SemanticTree is a sealed data structure that encodes tree nodes that are generated by the compiler from inferred type parameters, implicit arguments, implicit conversions, inferred .apply and for-comprehensions.

sealed abstract class SemanticTree extends Product with Serializable {
  final override def toString: String = Pretty.pretty(this).render(80)
  final def toString(width: Int): String = Pretty.pretty(this).render(width)
  final def isEmpty: Boolean = this == NoTree
  final def nonEmpty: Boolean = !isEmpty
}
final case class IdTree(info: SymbolInformation) extends SemanticTree { def symbol: Symbol = info.symbol }
final case class SelectTree(qualifier: SemanticTree, id: IdTree) extends SemanticTree
final case class ApplyTree(function: SemanticTree, arguments: List[SemanticTree]) extends SemanticTree
final case class TypeApplyTree(function: SemanticTree, typeArguments: List[SemanticType]) extends SemanticTree
final case class FunctionTree(parameters: List[IdTree], body: SemanticTree) extends SemanticTree
final case class LiteralTree(constant: Constant) extends SemanticTree
final case class MacroExpansionTree(beforeExpansion: SemanticTree, tpe: SemanticType) extends SemanticTree
final case class OriginalSubTree(tree: scala.meta.Tree) extends SemanticTree
final case class OriginalTree(tree: scala.meta.Tree) extends SemanticTree
case object NoTree extends SemanticTree

Cookbook

All code examples in this document assume you have the following imports in scope

import scalafix.v1._
import scala.meta._

The variable doc in the code examples is an implicit instance of scalafix.v1.SemanticDoc.

Look up inferred type parameter

Consider the following program.

Option(1) // inferred type parameter

Use Tree.synthetic on the qualifier Option.apply to get the inferred type parameters

doc.tree.traverse {
  // Option.apply
  // case option @ Term.Select(Term.Name("Option"), Term.Name("apply")) =>
  case Term.Apply(option, _) =>
    println("synthetic = " + option.synthetic)
    println("structure = " + option.synthetic.structure)
}
// synthetic = Some(*.apply[Int])
// structure = Some(TypeApplyTree(
//   SelectTree(
//     OriginalTree(Term.Name("Option")),
//     IdTree(SymbolInformation(scala/Option.apply(). => method apply[A](x: A): Option[A]))
//   ),
//   List(TypeRef(NoType, Symbol("scala/Int#"), List()))
// ))

The asterisk * represents an OriginalTree node that matches the enclosing non-synthetic tree, which is List in this example.

The .synthetic method is only available on Term nodes, using the method on other tree nodes such as types results in compilation error

doc.tree.traverse {
  case app @ Type.Name("App") =>
    println(".synthetic = " + app.synthetic)
}
// value synthetic is not a member of scala.meta.Type.Name
//     println(".synthetic = " + app.synthetic)
//                                   ^

Look up symbol of semantic tree

Consider the following program.

val add: Int => Int = _ + 1
add(2)         // inferred: add.apply(2)
Option[Int](2) // inferred: Option.apply[Int](2)

Use Tree.synthetic in combination with SemanticTree.symbol to get the symbol of those inferred .apply method calls.

doc.tree.traverse {
  case Term.Apply(add @ q"add", List(q"2")) =>
    println("add(2)")
    println("synthetic = " + add.synthetic)
    println("symbol    = " + add.synthetic.flatMap(_.symbol).structure)
    println("structure = " + add.synthetic.structure)
  case Term.ApplyType(option @ q"Option", List(t"Int")) =>
    println("Option[Int]")
    println("synthetic = " + option.synthetic)
    println("symbol    = " + option.synthetic.flatMap(_.symbol).structure)
    println("structure = " + option.synthetic.structure)
}
// add(2)
// synthetic = Some(*.apply)
// symbol    = Some(Symbol("scala/Function1#apply()."))
// structure = Some(SelectTree(
//   OriginalTree(Term.Name("add")),
//   IdTree(SymbolInformation(scala/Function1#apply(). => abstract method apply(v1: T1): R))
// ))
// Option[Int]
// synthetic = Some(*.apply)
// symbol    = Some(Symbol("scala/Option.apply()."))
// structure = Some(SelectTree(
//   OriginalTree(Term.Name("Option")),
//   IdTree(SymbolInformation(scala/Option.apply(). => method apply[A](x: A): Option[A]))
// ))

The .symbol method returns nothing for the following semantic trees

  • MacroExpansionTree
  • LiteralTree
  • FunctionTree
  • NoTree

Look up implicit argument

Consider the following program.

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
def run(implicit message: String) = println(message)
implicit val message = "hello world"

Future.apply[Int](3) // implicit argument: global
Main.run             // implicit argument: message

Use Tree.synthetic to look up an implicit argument for any Term node.

doc.tree.traverse {
  case term: Term if term.synthetic.isDefined =>
    println("term      = " + term.syntax)
    println("synthetic = " + term.synthetic)
    println("structure = " + term.synthetic.structure)
}
// term      = Future.apply[Int](3)
// synthetic = Some(*(global))
// structure = Some(ApplyTree(
//   OriginalTree(Term.Apply(
//     Term.ApplyType(
//       Term.Select(Term.Name("Future"), Term.Name("apply")),
//       List(Type.Name("Int"))
//     ),
//     List(Lit.Int(3))
//   )),
//   List(
//     IdTree(SymbolInformation(scala/concurrent/ExecutionContext.Implicits.global. => implicit lazy val method global: ExecutionContext))
//   )
// ))
// term      = Main.run
// synthetic = Some(*(message))
// structure = Some(ApplyTree(
//   OriginalTree(Term.Select(Term.Name("Main"), Term.Name("run"))),
//   List(
//     IdTree(SymbolInformation(_empty_/Main.message. => implicit val method message: String))
//   )
// ))

Look up inferred type parameters for infix operators

Infix operators such as a ++ b can have type parameters like a ++[Int] b. Consider the following program.

List(1) ++ List(2)

Use the Term.ApplyInfix.syntheticOperator to look up inferred type parameters of infix operators.

doc.tree.traverse {
  case concat @ Term.ApplyInfix(_, Term.Name("++"), _, _) =>
    println(".syntheticOperator = " + concat.syntheticOperator)
    println(".structure         = " + concat.syntheticOperator.structure)
}
// .syntheticOperator = Some(*[Int,List[Int]])
// .structure         = Some(TypeApplyTree(
//   OriginalTree(Term.ApplyInfix(
//     Term.Apply(Term.Name("List"), List(Lit.Int(1))),
//     Term.Name("++"),
//     List(),
//     List(Term.Apply(Term.Name("List"), List(Lit.Int(2))))
//   )),
//   List(
//     TypeRef(NoType, Symbol("scala/Int#"), List()),
//     TypeRef(
//       NoType,
//       Symbol("scala/collection/immutable/List#"),
//       List(TypeRef(NoType, Symbol("scala/Int#"), List()))
//     )
//   )
// ))

The .syntheticOperator method is only available for Term.ApplyInfix nodes, using the method on other node types results in a compilation error

doc.tree.traverse {
  case concat @ Term.Name("++") =>
    println(".syntheticOperator = " + concat.syntheticOperator)
}
// value syntheticOperator is not a member of scala.meta.Term.Name
//     println(".syntheticOperator = " + concat.syntheticOperator)
//                                              ^

Beware that looking up synthetics for the infix operator name returns nothing

doc.tree.traverse {
  case concat @ Term.Name("++") =>
    println(".synthetic = " + concat.synthetic)
}
// .synthetic = None

Look up for comprehension desugaring

Consider the following program.

val numbers = for {
  i <- List(1, 2)
  j <- 1.to(i)
} yield i + j
for (number <- numbers) println(number)

Use Tree.synthetic on the tree node Term.ForYield to inspect the desugared version of the for { .. } yield expression

doc.tree.traverse {
  case forYield: Term.ForYield =>
    println(".synthetic = " + forYield.synthetic)
}
// .synthetic = Some(orig(List(1, 2)).flatMap[Int,List[Int]](
//   { (i) =>
//     orig(1.to(i)).map[Int,IndexedSeq[Int]]({ (j) => orig(i + j) })(
//       canBuildFrom[Int]
//     )
//   }
// )(canBuildFrom[Int]))

The orig(List(1, 2)) and orig(1.to(i) parts represent OriginalSubTree nodes that match non-synthetic tree nodes from the original for-comprension.

Known limitations

The SemanticTree data structure does not encode all possible synthetic code that may get generated at compile-time. See scalameta/scalameta#1711 if you are interested in contributing to improve the situation.

for patterns

For comprehensions that use patterns produce incomplete semantic trees.

for {
  (a, b) <- List(1 -> 2) // pattern
} yield a + b

Observe the empty withFilter body and <unknown> parameter symbol.

doc.tree.traverse {
  case forYield: Term.ForYield =>
    println(forYield.synthetic)
}
// Some(
//   orig(List(1 -> 2)).withFilter(
//     { (check$ifrefutable$1) =>
//       
//     }
//   ).map[Int,List[Int]](
//     { (<unknown>) =>
//       
//     }
//   )(
//     canBuildFrom[Int]
//   )
// )

for assignments

For comprehensions that use assignments produce incomplete semantic trees.

for {
  a <- List(1)
  b = a + 1 // assignment
} yield a + b

Observe the <unknown> parameter symbol to the final call to map.

doc.tree.traverse {
  case forYield: Term.ForYield =>
    println(forYield.synthetic)
}
// Some(
//   orig(List(1)).map[Tuple2[Int,Int],List[Tuple2[Int,Int]]](
//     { (a) =>
//       orig(b = a + 1)
//     }
//   )(
//     canBuildFrom[Tuple2[Int,Int]]
//   ).map[Int,List[Int]](
//     { (<unknown>) =>
//       orig(a + b)
//     }
//   )(
//     canBuildFrom[Int]
//   )
// )

Macros

Macros can expand into arbitrary code including new definitions such as methods and classes. Semantic trees only encode tree nodes that are generated by the compiler through offical language features like type inference and implicit search.

SemanticDB

The structure of SemanticTree mirrors SemanticDB Tree. Consult the SemanticDB specification for more details about synthetics:

  • General trees
  • Scala trees

The SemanticTree data structure diverges from SemanticDB Tree in few minor details.

SemanticTree instead of Tree

Scalafix uses the name SemanticTree instead of Tree in order to avoid ambiguous references with scala.meta.Tree when importing the two packages together.

import scalafix.v1._
import scala.meta._

OriginalTree vs. OriginalSubTree

Scalafix has two kinds of original trees, OriginalTree and OriginalSubTree, while the SemanticDB specification has only one OriginalTree. The difference between the two is that

  • OriginalTree represents synthetic trees whose range match exactly the enclosing Synthetic range.
  • OriginalSubTree represents trees whose range is smaller than the enclosing Synthetic range.

This change avoids the need for the SemanticDB Synthetic type.

← PreviousNext →
  • Cookbook
    • Look up inferred type parameter
    • Look up symbol of semantic tree
    • Look up implicit argument
    • Look up inferred type parameters for infix operators
    • Look up for comprehension desugaring
  • Known limitations
    • for patterns
    • for assignments
    • Macros
  • SemanticDB
    • SemanticTree instead of Tree
    • OriginalTree vs. OriginalSubTree
Scalafix
Docs
Get startedRulesExtend Scalafix
Community
Chat on GitterDiscuss on Scala Users
More
GitHub
Copyright © 2018 Scala Center