diff --git a/scalameta/trees/shared/src/main/scala/scala/meta/internal/trees/Unary.scala b/scalameta/trees/shared/src/main/scala/scala/meta/internal/trees/Unary.scala new file mode 100644 index 0000000000..3f3bbb0243 --- /dev/null +++ b/scalameta/trees/shared/src/main/scala/scala/meta/internal/trees/Unary.scala @@ -0,0 +1,65 @@ +package scala.meta.internal.trees + +import scala.meta.tokens.Token + +private[meta] sealed trait Unary { + def op: String +} + +private[meta] object Unary { + + private val numericOpMap = Seq[Numeric](Plus, Minus, Tilde).map(x => x.op -> x).toMap + val opMap = numericOpMap ++ Seq[Unary](Not).map(x => x.op -> x) + + def unapply(token: Token.Ident): Option[(String, Unary)] = { + val op = token.text + opMap.get(op).map(op -> _) + } + + sealed trait Numeric extends Unary { + def apply(value: BigInt): BigInt + // could return None if not applicable (such as `~`) + def apply(value: BigDecimal): Option[BigDecimal] + } + + object Numeric { + def unapply(token: Token.Ident): Option[(String, Numeric)] = { + val op = token.text + numericOpMap.get(op).map(op -> _) + } + } + + sealed trait Logical extends Unary { + def apply(value: Boolean): Boolean + } + + case object Noop extends Numeric { + val op = "" + def apply(value: BigInt): BigInt = value + def apply(value: BigDecimal): Option[BigDecimal] = Some(value) + } + + case object Plus extends Numeric { + val op = "+" + def apply(value: BigInt): BigInt = value + def apply(value: BigDecimal): Option[BigDecimal] = Some(value) + } + + case object Minus extends Numeric { + val op = "-" + def apply(value: BigInt): BigInt = -value + def apply(value: BigDecimal): Option[BigDecimal] = Some(-value) + } + + case object Tilde extends Numeric { + val op = "~" + def apply(value: BigInt): BigInt = ~value + def apply(value: BigDecimal): Option[BigDecimal] = None + } + + case object Not extends Logical { + val op = "!" + def apply(value: Boolean): Boolean = !value + } + +} diff --git a/scalameta/trees/shared/src/main/scala/scala/meta/internal/trees/package.scala b/scalameta/trees/shared/src/main/scala/scala/meta/internal/trees/package.scala index abbaeb58b5..bb06ee8c61 100644 --- a/scalameta/trees/shared/src/main/scala/scala/meta/internal/trees/package.scala +++ b/scalameta/trees/shared/src/main/scala/scala/meta/internal/trees/package.scala @@ -43,7 +43,7 @@ package object trees { // some heuristic is needed to govern associativity and precedence of unquoted operators def isLeftAssoc: Boolean = value.last != ':' - def isUnaryOp: Boolean = Set("-", "+", "~", "!").contains(value) + def isUnaryOp: Boolean = Unary.opMap.contains(value) def isAssignmentOp = value match { case "!=" | "<=" | ">=" | "" => false diff --git a/tests/shared/src/test/scala/scala/meta/tests/trees/TreeSuite.scala b/tests/shared/src/test/scala/scala/meta/tests/trees/TreeSuite.scala index 30a4991d63..f9450e5e44 100644 --- a/tests/shared/src/test/scala/scala/meta/tests/trees/TreeSuite.scala +++ b/tests/shared/src/test/scala/scala/meta/tests/trees/TreeSuite.scala @@ -3,10 +3,29 @@ package trees import munit._ import scala.meta._ +import scala.meta.internal.trees._ class TreeSuite extends FunSuite { test("Name.unapply") { assert(Name.unapply(q"a").contains("a")) assert(Name.unapply(t"a").contains("a")) } + + Seq( + ("+", Unary.Plus), + ("-", Unary.Minus), + ("~", Unary.Tilde), + ("!", Unary.Not) + ).foreach { case (op, unary) => + test(s"Unary.$unary") { + assertEquals(unary.op, op) + assertEquals(Unary.opMap.get(op), Some(unary)) + assert(op.isUnaryOp) + } + } + + test(s"Unary opMap size") { + assertEquals(Unary.opMap.size, 4) + } + }