This doc page is specific to features shipped in Scala 2, which have either been removed in Scala 3 or replaced by an alternative. Unless otherwise stated, all the code examples in this page assume you are using Scala 2.
Denys Shabalin EXPERIMENTAL
Unlifting is the reverse operation to lifting: it takes a tree and recovers a value from it:
trait Unliftable[T] {
def unapply(tree: Tree): Option[T]
}
Due to the fact that the tree may not be a representation of our data type, the return type of unapply is Option[T]
rather than just T
. This signature makes it easy to use Unliftable
instances as extractors.
Whenever an implicit instance of Unliftable
is available for a given data type you can use it for pattern matching with the help of an ascription syntax:
scala> val q"${left: Int} + ${right: Int}" = q"2 + 2"
left: Int = 2
right: Int = 2
scala> left + right
res4: Int = 4
It’s important to note that unlifting will not be performed at locations where Name
, TermName
or Modifiers
are extracted by default:
scala> val q"foo.${bar: Int}" = q"foo.bar"
<console>:29: error: pattern type is incompatible with expected type;
found : Int
required: universe.NameApi
val q"foo.${bar: Int}" = q"foo.bar"
^
One can also successfully combine unquote splicing and unlifting:
scala> val q"f(..${ints: List[Int]})" = q"f(1, 2, 3)"
ints: List[Int] = List(1, 2, 3)
scala> val q"f(...${intss: List[List[Int]]})" = q"f(1, 2, 3)(4, 5)(6)"
intss: List[List[Int]] = List(List(1, 2, 3), List(4, 5), List(6))
Analogously to lifting, this would unlift arguments of the function, element-wise and wrap the result into a List
.
Bring your own
Similarly to liftables one can define your own unliftables:
package Points
import scala.universe._
case class Point(x: Int, y: Int)
object Point {
implicit val unliftPoint = Unliftable[points.Point] {
case q"_root_.points.Point(${x: Int}, ${y: Int})" => Point(x, y)
}
}
Here one must pay attention to a few nuances:
-
Similarly to
Liftable
,Unliftable
defines a helperapply
function in the companion object to simplify the creation ofUnliftable
instances. It takes a type parameterT
as well as a partial functionPartialFunction[Tree, T]
and returns anUnliftable[T]
. At all inputs where a partial function is defined it is expected to return an instance ofT
unconditionally. -
We’ve only define
Unliftable
for the runtime universe, it won’t be available in macros. (see sharing liftable implementations) -
Patterns used in this unliftable will only match a fully qualified reference to
Point
that starts with_root_
. It won’t match other possible shapes of the reference; they have to be specified by hand. This problem is caused by a lack of hygiene. -
The pattern will only match trees that have literal
Int
arguments. It won’t work for other expressions that might evaluate toInt
.
Standard Unliftables
Type | Representation | Value |
---|---|---|
Byte , Short , Int , Long |
q"0" |
0 |
Float |
q"0.0" |
0.0 |
Double |
q"0.0D" |
0.0D |
Boolean |
q"true" , q"false" |
true , false |
Char |
q"'c'" |
'c' |
Unit |
q"()" |
() |
String |
q""" "string" """ |
"string" |
Symbol |
q"'symbol" |
'symbol |
TermName |
q"foo" , pq"foo" |
TermName("foo") |
TypeName |
tq"foo" |
TypeName("foo") |
Type |
tt: TypeTree |
tt.tpe |
Constant |
lit: Literal |
lit.value |
TupleN[...] * |
q"(1, 2)" |
(1, 2) |
(*) Unliftable for tuples is defined for all N in [2, 22] range. All type parameters have to be Unliftable themselves.