import org.scalacheck._, Prop._, Gen._, Arbitrary._
import scala.reflect.runtime.universe._, Flag._, internal.reificationSupport.SyntacticClassDef
object DefinitionDeconstructionProps
extends QuasiquoteProperties("definition deconstruction")
with TraitDeconstruction
with ClassDeconstruction
with ObjectDeconstruction
with ModsDeconstruction
with ValVarDeconstruction
with DefDeconstruction
with PackageDeconstruction
with ImportDeconstruction
trait TraitDeconstruction { self: QuasiquoteProperties =>
property("exhaustive trait matcher") = test {
def matches(line: String) {
val q"""$mods trait $name[..$targs]
extends { ..$early } with ..$parents { $self => ..$body }""" = parse(line)
}
matches("trait Foo")
matches("trait Foo[T]")
matches("trait Foo { def bar }")
matches("trait Foo extends Bar with Baz")
matches("trait Foo { self: Bippy => val x: Int = 1}")
matches("trait Foo extends { val early: Int = 1 } with Bar { val late = early }")
matches("private[Gap] trait Foo")
}
}
trait ObjectDeconstruction { self: QuasiquoteProperties =>
property("exhaustive object matcher") = test {
def matches(line: String) = {
val q"""$mods object $name extends { ..$early } with ..$parents { $self => ..$body }""" = parse(line)
}
matches("object Foo")
matches("object Foo extends Bar[T]")
matches("object Foo extends { val early: T = v } with Bar")
matches("object Foo extends Foo { selfy => body }")
matches("private[Bippy] object Foo extends Bar with Baz")
}
}
trait ClassDeconstruction { self: QuasiquoteProperties =>
property("class without params") = test {
val q"class $name { ..$body }" = q"class Foo { def bar = 3 }"
assert(body ≈ List(q"def bar = 3"))
}
property("class constructor") = test {
val q"class $name(...$argss)" = q"class Foo(x: Int)(y: Int)"
assert(argss.length == 2)
}
property("class parents") = test {
val q"class $name extends ..$parents" = q"class Foo extends Bar with Blah"
assert(parents ≈ List(tq"Bar", tq"Blah"))
}
property("class selfdef") = test {
val q"class $name { $self => }" = q"class Foo { self: T => }"
assert(self.name ≈ TermName("self") && self.tpt ≈ tq"T")
}
property("class tparams") = test {
val q"class $name[..$tparams]" = q"class Foo[A, B]"
assert(tparams.map { _.name } == List(TypeName("A"), TypeName("B")))
}
property("deconstruct bare case class") = test {
val q"$mods class $name(..$args) extends ..$parents" = q"case class Foo(x: Int)"
}
property("exhaustive class matcher") = test {
def matches(line: String) {
val q"""$classMods class $name[..$targs] $ctorMods(...$argss)
extends { ..$early } with ..$parents { $self => ..$body }""" = parse(line)
}
matches("class Foo")
matches("class Foo[T]")
matches("class Foo[T] @annot")
matches("class Foo extends Bar with Baz")
matches("class Foo { body }")
matches("class Foo extends { val early = 0 } with Any")
matches("abstract class Foo")
matches("private[Baz] class Foo")
matches("class Foo(first: A)(second: B)")
matches("class Foo(first: A) extends Bar(first) with Baz")
matches("class Foo private (first: A) { def bar }")
matches("class Foo { self => bar(self) }")
matches("case class Foo(x: Int)")
}
property("SI-7979") = test {
val PARAMACCESSOR = (1 << 29).toLong.asInstanceOf[FlagSet]
assertThrows[MatchError] {
val SyntacticClassDef(_, _, _, _, _, _, _, _, _) =
ClassDef(
Modifiers(), TypeName("Foo"), List(),
Template(
List(Select(Ident(TermName("scala")), TypeName("AnyRef"))),
noSelfType,
List(
//ValDef(Modifiers(PRIVATE | LOCAL | PARAMACCESSOR), TermName("x"), Ident(TypeName("Int")), EmptyTree),
DefDef(Modifiers(), termNames.CONSTRUCTOR, List(), List(List(ValDef(Modifiers(PARAM | PARAMACCESSOR), TermName("x"),
Ident(TypeName("Int")), EmptyTree))), TypeTree(), Block(List(pendingSuperCall), Literal(Constant(())))))))
}
}
}
trait ModsDeconstruction { self: QuasiquoteProperties =>
property("deconstruct mods") = test {
val mods = Modifiers(IMPLICIT | PRIVATE, TermName("foobar"), Nil)
val q"$mods0 def foo" = q"$mods def foo"
assert(mods0 ≈ mods)
}
property("@$annot def foo") = forAll { (annotName: TypeName) =>
val q"@$annot def foo" = q"@$annotName def foo"
annot ≈ Apply(Select(New(Ident(annotName)), termNames.CONSTRUCTOR), List())
}
property("@$annot(..$args) def foo") = forAll { (annotName: TypeName, tree: Tree) =>
val q"@$annot(..$args) def foo" = q"@$annotName($tree) def foo"
annot ≈ Ident(annotName) && args ≈ List(tree)
}
property("@..$annots def foo") = test {
val a = q"new a"
val b = q"new b"
val q"@..$annots def foo" = q"@$a @$b def foo"
annots ≈ List(a, b)
}
property("@$annot @..$annots def foo") = test {
val a = q"new a"
val b = q"new b"
val c = q"new c"
val q"@$first @..$rest def foo" = q"@$a @$b @$c def foo"
assert(first ≈ a)
assert(rest ≈ List(b, c))
}
property("@..$anots @$annot def foo") = test {
val a = q"new a"
val b = q"new b"
val c = q"new c"
val q"@..$init @$last def foo" = q"@$a @$b @$c def foo"
assert(init ≈ List(a, b))
assert(last ≈ c)
}
}
trait ValVarDeconstruction { self: QuasiquoteProperties =>
property("exhaustive val matcher") = test {
def matches(line: String) { val q"$mods val $name: $tpt = $rhs" = parse(line) }
matches("val x: Int")
matches("val x: Int = 1")
matches("lazy val x: Int = 1")
matches("implicit val x = 1")
assertThrows[MatchError] { matches("var x = 1") }
}
property("exhaustive var matcher") = test {
def matches(line: String) { val q"$mods var $name: $tpt = $rhs" = parse(line) }
matches("var x: Int")
matches("var x: Int = 1")
matches("var x = 1")
assertThrows[MatchError] { matches("val x = 1") }
}
}
trait PackageDeconstruction { self: QuasiquoteProperties =>
property("exhaustive package matcher") = test {
def matches(line: String) { val q"package $name { ..$body }" = parse(line) }
matches("package foo { }")
matches("package foo { class C }")
matches("package foo.bar { }")
matches("package bippy.bongo { object A; object B }")
matches("package bippy { package bongo { object O } }")
}
property("exhaustive package object matcher") = test {
def matches(line: String) {
val q"package object $name extends { ..$early } with ..$parents { $self => ..$body }" = parse(line)
}
matches("package object foo")
matches("package object foo { def baz }")
matches("package object foo { self => }")
matches("package object foo extends mammy with daddy { def baz }")
matches("package object foo extends { val early = 1 } with daddy")
assertThrows[MatchError] { matches("object foo") }
}
}
trait DefDeconstruction { self: QuasiquoteProperties =>
property("exhaustive def matcher") = test {
def matches(line: String) = {
val t = parse(line)
val q"$mods0 def $name0[..$targs0](...$argss0): $restpe0 = $body0" = t
val q"$mods1 def $name1[..$targs1](...$argss1)(implicit ..$impl1): $restpe1 = $body1" = t
}
matches("def foo = foo")
matches("implicit def foo: Int = 2")
matches("def foo[T](x: T): T = x")
matches("def foo[A: B] = implicitly[B[A]]")
matches("private def foo = 0")
matches("def foo[A <% B] = null")
matches("def foo(one: One)(two: Two) = (one, two)")
matches("def foo[T](args: T*) = args.toList")
}
property("extract implicit arg list (1)") = test {
val q"def foo(...$argss)(implicit ..$impl)" = q"def foo(x: Int)(implicit y: Int)"
assert(impl ≈ List(q"${Modifiers(IMPLICIT | PARAM)} val y: Int"))
}
property("extract implicit arg list (2)") = test {
val q"def foo(...$argss)(implicit ..$impl)" = q"def foo(x: Int)"
assert(impl.isEmpty)
}
}
trait ImportDeconstruction { self: QuasiquoteProperties =>
property("exhaustive import matcher") = test {
def matches(line: String) = {
val q"import $ref.{..$sels}" = parse(line)
}
matches("import foo.bar")
matches("import foo.{bar, baz}")
matches("import foo.{a => b, c => d}")
matches("import foo.{poision => _, _}")
matches("import foo.bar.baz._")
}
property("extract import binding") = test {
val q"import $_.$sel" = q"import foo.bar"
val pq"bar" = sel
}
property("extract import wildcard") = test {
val q"import $_.$sel" = q"import foo._"
val pq"_" = sel
}
property("extract import rename") = test {
val q"import $_.$sel" = q"import foo.{bar => baz}"
val pq"bar -> baz" = sel
val pq"$left -> $right" = sel
val pq"bar" = left
val pq"baz" = right
}
property("extract import unimport") = test {
val q"import $_.$sel" = q"import foo.{bar => _}"
val pq"bar -> _" = sel
val pq"$left -> $right" = sel
val pq"bar" = left
val pq"_" = right
}
property("splice names into import selector") = forAll {
(expr: Tree, plain: TermName, oldname: TermName, newname: TermName, discard: TermName) =>
val Import(expr1, List(
ImportSelector(plain11, _, plain12, _),
ImportSelector(oldname1, _, newname1, _),
ImportSelector(discard1, _, wildcard, _))) =
q"import $expr.{$plain, $oldname => $newname, $discard => _}"
expr1 ≈ expr && plain11 == plain12 && plain12 == plain &&
oldname1 == oldname && newname1 == newname && discard1 == discard && wildcard == termNames.WILDCARD
}
}