diff --git a/build.sbt b/build.sbt index 11804a2ccec1..2413a3402e2c 100644 --- a/build.sbt +++ b/build.sbt @@ -13,6 +13,7 @@ val `dotty-language-server` = Build.`dotty-language-server` val `dotty-bench` = Build.`dotty-bench` val `dotty-bench-bootstrapped` = Build.`dotty-bench-bootstrapped` val `dotty-semanticdb` = Build.`dotty-semanticdb` +val `dotty-semanticdb-input` = Build.`dotty-semanticdb-input` val `scala-library` = Build.`scala-library` val `scala-compiler` = Build.`scala-compiler` val `scala-reflect` = Build.`scala-reflect` diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/PositionOpsImpl.scala b/compiler/src/dotty/tools/dotc/tastyreflect/PositionOpsImpl.scala index 5fce3371dfa4..66d3bcac08f9 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/PositionOpsImpl.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/PositionOpsImpl.scala @@ -6,6 +6,8 @@ trait PositionOpsImpl extends scala.tasty.reflect.PositionOps with CoreImpl { def start: Int = pos.start def end: Int = pos.end + def exists: Boolean = pos.exists + def sourceFile: java.nio.file.Path = pos.source.file.jpath def startLine: Int = pos.startLine @@ -14,5 +16,4 @@ trait PositionOpsImpl extends scala.tasty.reflect.PositionOps with CoreImpl { def startColumn: Int = pos.startColumn def endColumn: Int = pos.endColumn } - } diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/SymbolOpsImpl.scala b/compiler/src/dotty/tools/dotc/tastyreflect/SymbolOpsImpl.scala index b0be74120ce9..155dd16acbd8 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/SymbolOpsImpl.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/SymbolOpsImpl.scala @@ -26,6 +26,8 @@ trait SymbolOpsImpl extends scala.tasty.reflect.SymbolOps with CoreImpl { def name(implicit ctx: Context): String = symbol.name.toString def fullName(implicit ctx: Context): String = symbol.fullName.toString + def pos(implicit ctx: Context): Position = symbol.pos + def owner(implicit ctx: Context): Symbol = symbol.owner def localContext(implicit ctx: Context): Context = { diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/TypeOrBoundsTreesOpsImpl.scala b/compiler/src/dotty/tools/dotc/tastyreflect/TypeOrBoundsTreesOpsImpl.scala index 5532f327ae92..1622844052e2 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/TypeOrBoundsTreesOpsImpl.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/TypeOrBoundsTreesOpsImpl.scala @@ -18,8 +18,8 @@ trait TypeOrBoundsTreesOpsImpl extends scala.tasty.reflect.TypeOrBoundsTreeOps w def TypeTreeDeco(tpt: TypeTree): TypeTreeAPI = new TypeTreeAPI { def pos(implicit ctx: Context): Position = tpt.pos - def tpe(implicit ctx: Context): Type = tpt.tpe.stripTypeVar def symbol(implicit ctx: Context): Symbol = tpt.symbol + def tpe(implicit ctx: Context): Type = tpt.tpe.stripTypeVar } object IsTypeTree extends IsTypeTreeExtractor { diff --git a/library/src/scala/tasty/reflect/PositionOps.scala b/library/src/scala/tasty/reflect/PositionOps.scala index 7b5d677f109d..aba9fd344a02 100644 --- a/library/src/scala/tasty/reflect/PositionOps.scala +++ b/library/src/scala/tasty/reflect/PositionOps.scala @@ -4,7 +4,8 @@ trait PositionOps extends Core { trait PositionAPI { - /** The path of source file */ + def exists: Boolean + def sourceFile: java.nio.file.Path /** The start index in the source file */ diff --git a/library/src/scala/tasty/reflect/SymbolOps.scala b/library/src/scala/tasty/reflect/SymbolOps.scala index b49a8b550017..33a006be1020 100644 --- a/library/src/scala/tasty/reflect/SymbolOps.scala +++ b/library/src/scala/tasty/reflect/SymbolOps.scala @@ -26,7 +26,8 @@ trait SymbolOps extends Core { /** The full name of this symbol up to the root package. */ def fullName(implicit ctx: Context): String - /** Returns the context within this symbol. */ + def pos(implicit ctx: Context): Position + def localContext(implicit ctx: Context): Context /** Unsafe cast as to PackageSymbol. Use IsPackageSymbol to safly check and cast to PackageSymbol */ diff --git a/library/src/scala/tasty/reflect/TreeUtils.scala b/library/src/scala/tasty/reflect/TreeUtils.scala index 7f8c72b8fab4..279675c56d8a 100644 --- a/library/src/scala/tasty/reflect/TreeUtils.scala +++ b/library/src/scala/tasty/reflect/TreeUtils.scala @@ -104,6 +104,7 @@ trait TreeUtils case TypeTree.Block(typedefs, tpt) => foldTypeTree(foldTrees(x, typedefs), tpt) case TypeTree.MatchType(boundopt, selector, cases) => foldTypeCaseDefs(foldTypeTree(boundopt.fold(x)(foldTypeTree(x, _)), selector), cases) + case SyntheticBounds() => x case TypeBoundsTree(lo, hi) => foldTypeTree(foldTypeTree(x, lo), hi) } diff --git a/library/src/scala/tasty/reflect/TypeOrBoundsTreeOps.scala b/library/src/scala/tasty/reflect/TypeOrBoundsTreeOps.scala index e783548c5526..c7f02b2678e8 100644 --- a/library/src/scala/tasty/reflect/TypeOrBoundsTreeOps.scala +++ b/library/src/scala/tasty/reflect/TypeOrBoundsTreeOps.scala @@ -28,6 +28,7 @@ trait TypeOrBoundsTreeOps extends Core { val TypeTree: TypeTreeModule abstract class TypeTreeModule { + /** TypeTree containing an inferred type */ val Synthetic: SyntheticExtractor abstract class SyntheticExtractor { diff --git a/project/Build.scala b/project/Build.scala index de79d4d8a79b..ef19c1f1bc92 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -393,9 +393,9 @@ object Build { baseDirectory in Test := baseDirectory.value / "..", unmanagedSourceDirectories in Test += baseDirectory.value / "input" / "src" / "main" / "scala", libraryDependencies ++= List( - ("org.scalameta" %% "semanticdb" % "4.0.0" % Test).withDottyCompat(scalaVersion.value), - "com.novocode" % "junit-interface" % "0.11" % Test, - "com.googlecode.java-diff-utils" % "diffutils" % "1.3.0" % Test + ("org.scalameta" %% "semanticdb" % "4.0.0").withDottyCompat(scalaVersion.value), + "com.novocode" % "junit-interface" % "0.11", + "com.googlecode.java-diff-utils" % "diffutils" % "1.3.0" ) ) @@ -919,6 +919,11 @@ object Build { lazy val `dotty-bench-bootstrapped` = project.in(file("bench")).asDottyBench(Bootstrapped) lazy val `dotty-semanticdb` = project.in(file("semanticdb")).asDottySemanticdb(Bootstrapped) + lazy val `dotty-semanticdb-input` = project.in(file("semanticdb/input")).settings( + scalaVersion := "2.12.7", + scalacOptions += "-Yrangepos", + addCompilerPlugin("org.scalameta" % "semanticdb-scalac" % "4.0.0" cross CrossVersion.full) + ) // Depend on dotty-library so that sbt projects using dotty automatically // depend on the dotty-library @@ -1317,6 +1322,7 @@ object Build { enablePlugins(JmhPlugin) def asDottySemanticdb(implicit mode: Mode): Project = project.withCommonSettings. + aggregate(`dotty-semanticdb-input`). dependsOn(dottyCompiler). settings(semanticdbSettings) diff --git a/semanticdb/input/src/main/scala/example/Access.scala b/semanticdb/input/src/main/scala/example/Access.scala new file mode 100644 index 000000000000..aee3754da66b --- /dev/null +++ b/semanticdb/input/src/main/scala/example/Access.scala @@ -0,0 +1,11 @@ +package example + +class Access { + private def m1 = ??? + private[this] def m2 = ??? + private[Access] def m3 = ??? + protected def m4 = ??? + protected[this] def m5 = ??? + protected[example] def m6 = ??? + def m7 = ??? +} diff --git a/semanticdb/input/src/main/scala/example/Advanced.scala b/semanticdb/input/src/main/scala/example/Advanced.scala new file mode 100644 index 000000000000..fdee5eccad19 --- /dev/null +++ b/semanticdb/input/src/main/scala/example/Advanced.scala @@ -0,0 +1,39 @@ +package example + +import scala.language.existentials +import scala.language.higherKinds +import scala.language.reflectiveCalls + +class AdvC[T] { + def t: T = ??? +} + +class Structural { + def s1: { val x: Int } = ??? + def s2 = new { val x: Int = ??? } + def s3 = new { def m(x: Int): Int = ??? } +} + +class Existential { + def e1: List[_] = ??? +} + +class AdvD[CC[B]] extends AdvC[CC[B]] + +object AdvTest { + val s = new Structural + val s1 = s.s1 + val s2 = s.s2 + val s3 = s.s3 + + val e = new Existential + val e1 = e.e1 + val e1x = e.e1.head + locally { + (??? : Any) match { + case e3: List[_] => + val e3x = e3.head + () + } + } +} diff --git a/semanticdb/input/src/main/scala/example/Anonymous.scala b/semanticdb/input/src/main/scala/example/Anonymous.scala new file mode 100644 index 000000000000..aae065fa7ae3 --- /dev/null +++ b/semanticdb/input/src/main/scala/example/Anonymous.scala @@ -0,0 +1,18 @@ +package example +import scala.language.higherKinds + +class Anonymous { + this: Anonymous => + + def m1[T[_], B] = ??? + def m2: Map[_, List[_]] = ??? + locally { + ??? match { case _: List[_] => } + } + locally { + val x: Int => Int = _ => ??? + } + + trait Foo + var x = new Foo {} +} diff --git a/semanticdb/input/src/main/scala/example/Classes.scala b/semanticdb/input/src/main/scala/example/Classes.scala new file mode 100644 index 000000000000..b82f2c030ff0 --- /dev/null +++ b/semanticdb/input/src/main/scala/example/Classes.scala @@ -0,0 +1,33 @@ +package example + +class C1(val x1: Int) extends AnyVal + +class C2(val x2: Int) extends AnyVal +object C2 + +case class C3(x: Int) + +case class C4(x: Int) +object C4 + +object M { + implicit class C5(x: Int) +} + +case class C6(private val x: Int) + +class C7(x: Int) + +class C8(private[this] val x: Int) + +class C9(private[this] var x: Int) + +object N { + val anonClass = new C7(42) { + val local = ??? + } + val anonFun = List(1).map { i => + val local = 2 + local + 2 + } +} diff --git a/semanticdb/input/src/main/scala/example/Empty.scala b/semanticdb/input/src/main/scala/example/Empty.scala new file mode 100644 index 000000000000..eac850705562 --- /dev/null +++ b/semanticdb/input/src/main/scala/example/Empty.scala @@ -0,0 +1,9 @@ +package example + +class AdvA { + def b: AdvB = ??? +} + +class AdvB { + def a: AdvA = ??? +} diff --git a/semanticdb/input/src/main/scala/example/EmptyObject.scala b/semanticdb/input/src/main/scala/example/EmptyObject.scala new file mode 100644 index 000000000000..58773e9bd7eb --- /dev/null +++ b/semanticdb/input/src/main/scala/example/EmptyObject.scala @@ -0,0 +1,3 @@ +package example + +object EmptyObject {} diff --git a/semanticdb/input/src/main/scala/example/Example.scala b/semanticdb/input/src/main/scala/example/Example.scala index 00b34748ae19..f4eb0fbeb947 100644 --- a/semanticdb/input/src/main/scala/example/Example.scala +++ b/semanticdb/input/src/main/scala/example/Example.scala @@ -15,3 +15,7 @@ class Example { y ) } + +class ExampleInit() { + +} \ No newline at end of file diff --git a/semanticdb/input/src/main/scala/example/Example2.scala b/semanticdb/input/src/main/scala/example/Example2.scala new file mode 100644 index 000000000000..1135020e76db --- /dev/null +++ b/semanticdb/input/src/main/scala/example/Example2.scala @@ -0,0 +1,10 @@ +package example + +import scala.concurrent.Future + +object OExample { self => + def main(args: Array[String]): Unit = { + println(1) + } + val x = scala.reflect.classTag[Int] +} diff --git a/semanticdb/input/src/main/scala/example/Exclude.scala b/semanticdb/input/src/main/scala/example/Exclude.scala new file mode 100644 index 000000000000..35e085572ff5 --- /dev/null +++ b/semanticdb/input/src/main/scala/example/Exclude.scala @@ -0,0 +1,4 @@ +package example + +// This class should be excluded by semanticdb. +class Exclude diff --git a/semanticdb/input/src/main/scala/example/Flags.scala b/semanticdb/input/src/main/scala/example/Flags.scala new file mode 100644 index 000000000000..51c56d2fac47 --- /dev/null +++ b/semanticdb/input/src/main/scala/example/Flags.scala @@ -0,0 +1,24 @@ +package example + +package object p { + private lazy val x = 1 + protected implicit var y: Int = 2 + def z(pp: Int) = 3 + def m[TT] = ??? + abstract class C[+T, -U, V](x: T, y: U, z: V) { + def this() = this(???, ???, ???) + def w: Int + } + type T1 = Int + type T2[T] = S[T] + type U <: Int + type V >: Int + case object X + final class Y + sealed trait Z + class AA(x: Int, val y: Int, var z: Int) + class S[@specialized T] + val List(xs1) = ??? + ??? match { case List(xs2) => ??? } + ??? match { case _: List[t] => ??? } +} diff --git a/semanticdb/input/src/main/scala/example/Imports.scala b/semanticdb/input/src/main/scala/example/Imports.scala new file mode 100644 index 000000000000..5f4ead1f23cc --- /dev/null +++ b/semanticdb/input/src/main/scala/example/Imports.scala @@ -0,0 +1 @@ +//import scala.util.control.NonFatal diff --git a/semanticdb/input/src/main/scala/example/Issue1749.scala b/semanticdb/input/src/main/scala/example/Issue1749.scala new file mode 100644 index 000000000000..66f35aa00c88 --- /dev/null +++ b/semanticdb/input/src/main/scala/example/Issue1749.scala @@ -0,0 +1,11 @@ +// See https://github.com/scalameta/scalameta/issues/1749 +package example + +import scala.math.Ordered.orderingToOrdered + +class Issue1749 { + val x1 = 42 + val x2 = 42 + (x1, x1) + .compare((x2, x2)) +} diff --git a/semanticdb/input/src/main/scala/example/Locals.scala b/semanticdb/input/src/main/scala/example/Locals.scala new file mode 100644 index 000000000000..308a1e713846 --- /dev/null +++ b/semanticdb/input/src/main/scala/example/Locals.scala @@ -0,0 +1,8 @@ +package example + +object Test { + val xs = { + val x = 42 + List(x) + } +} diff --git a/semanticdb/input/src/main/scala/example/MacroAnnotations.scala b/semanticdb/input/src/main/scala/example/MacroAnnotations.scala new file mode 100644 index 000000000000..02276ed2ef6f --- /dev/null +++ b/semanticdb/input/src/main/scala/example/MacroAnnotations.scala @@ -0,0 +1,5 @@ +package example + +//@MacroAnnotation +class MacroAnnotations +object MacroAnnotations diff --git a/semanticdb/input/src/main/scala/example/MethodUsages.scala b/semanticdb/input/src/main/scala/example/MethodUsages.scala new file mode 100644 index 000000000000..ea0b9e93713f --- /dev/null +++ b/semanticdb/input/src/main/scala/example/MethodUsages.scala @@ -0,0 +1,33 @@ +package example + +class MethodUsages { + val m = new Methods[Int] + m.m1 + m.m2() + m.m3(0) + m.m4(0)(0) + m.m5("") + m.m5(0) + m.m6(0) + m.m6(new m.List[Int]) + m.m6(Nil) + m.m7a(m, new m.List[Int]) + m.m7b(new m.List[Int]) + m.`m8().`() + m.m9(null) + m.m10(null) + m.m11(Predef) + m.m11(OExample) + m.m12a(null) + m.m12b(null) + m.m13(0) + m.m15(0) + m.m16(0) + m.m16(0) + m.m17.m() + m.m17(1) + m.m17("") + m.m18.m() + m.m18(1) + m.m18("") +} diff --git a/semanticdb/input/src/main/scala/example/Methods.scala b/semanticdb/input/src/main/scala/example/Methods.scala new file mode 100644 index 000000000000..97af95b3c9c2 --- /dev/null +++ b/semanticdb/input/src/main/scala/example/Methods.scala @@ -0,0 +1,38 @@ +package example + +import scala.math.Ordering +import scala.language.existentials + +class Methods[T] { + class List[T] + type AList[T] = List[T] + def m1 = ??? + def m2() = ??? + def m3(x: Int) = ??? + def m4(x: Int)(y: Int) = ??? + def m5(x: String) = ??? + def m5(x: Int) = ??? + def m6(x: Int) = ??? + def m6(x: List[T]) = ??? + def m6(x: scala.List[T]) = ??? + def m7a[U: Ordering](c: Methods[T], l: List[U]) = ??? + def m7b[U <: T](l: List[U]) = ??? + def `m8().`() = ??? + class `m9().` + def m9(x: `m9().`) = ??? + def m10(x: AList[T]) = ??? + def m11(x: Predef.type) = ??? + def m11(x: OExample.type) = ??? + def m12a(x: {}) = ??? + def m12b(x: { val x: Int }) = ??? + def m13(x: Int @unchecked) = ??? + def m15(x: => Int) = ??? + def m16(x: Int*) = ??? + object m17 { def m() = ??? } + def m17(a: Int) = ??? + def m17(b: String) = ??? + val m18 = m17 + def m18(a: Int) = ??? + def m18(b: String) = ??? + def m19(x: Int, y: Int = 2)(z: Int = 3) = ??? +} diff --git a/semanticdb/input/src/main/scala/example/Objects.scala b/semanticdb/input/src/main/scala/example/Objects.scala new file mode 100644 index 000000000000..69fba5dfed3c --- /dev/null +++ b/semanticdb/input/src/main/scala/example/Objects.scala @@ -0,0 +1,5 @@ +package example + +object X { + object Y +} diff --git a/semanticdb/input/src/main/scala/example/Overrides.scala b/semanticdb/input/src/main/scala/example/Overrides.scala new file mode 100644 index 000000000000..3d13cf4823d8 --- /dev/null +++ b/semanticdb/input/src/main/scala/example/Overrides.scala @@ -0,0 +1,4 @@ +package example + +trait OveA { def foo: Int } +class OveB() extends OveA { def foo: Int = 2 } diff --git a/semanticdb/input/src/main/scala/example/Prefixes.scala b/semanticdb/input/src/main/scala/example/Prefixes.scala new file mode 100644 index 000000000000..10daaede1937 --- /dev/null +++ b/semanticdb/input/src/main/scala/example/Prefixes.scala @@ -0,0 +1,33 @@ +package example + +class PrefC { + type T + def m1: T = ??? + + object N { + type U + } + def k1: N.U = ??? +} + +object PrefM { + type T + def n1: T = ??? +} + +object PrefO extends PrefC { + def o1: T = ??? +} + +object PrefTest { + val c: PrefC = ??? + def m2: c.T = ??? + def k2: c.N.U = ??? + import c.N._ + def k3: U = ??? + + def n2: PrefM.T = ??? + + import PrefM._ + def n3: T = ??? +} diff --git a/semanticdb/input/src/main/scala/example/Selfs.scala b/semanticdb/input/src/main/scala/example/Selfs.scala new file mode 100644 index 000000000000..06ed2c26709a --- /dev/null +++ b/semanticdb/input/src/main/scala/example/Selfs.scala @@ -0,0 +1,24 @@ +package example + +class B + +class AC1 extends B { self => +} + +class AC2 extends B { self: B => +} + +abstract class AC3 extends B { self: B with Int => +} + +class AC4 extends B { a => +} + +class AC5 extends B { a : B => +} + +class AC6 extends B { this: B => +} + +abstract class AC7 { a: B => +} diff --git a/semanticdb/input/src/main/scala/example/Synthetic.scala b/semanticdb/input/src/main/scala/example/Synthetic.scala new file mode 100644 index 000000000000..c888edc329e8 --- /dev/null +++ b/semanticdb/input/src/main/scala/example/Synthetic.scala @@ -0,0 +1,47 @@ +package example + +class Synthetic { + List(1).map(_ + 2) + Array.empty[Int].headOption + "fooo".stripPrefix("o") + + // See https://github.com/scalameta/scalameta/issues/977 + val Name = "name:(.*)".r + val x #:: xs = Stream(1, 2) + val Name(name) = "name:foo" + 1 #:: 2 #:: Stream.empty + + val lst = 1 #:: 2 #:: Stream.empty + lst + "foo" + + for (x <- 1 to 10; y <- 0 until 10) println(x -> x) + for (i <- 1 to 10; j <- 0 until 10) yield (i, j) + for (i <- 1 to 10; j <- 0 until 10 if i % 2 == 0) yield (i, j) + + object s { + def apply() = 2 + s() + s.apply() + case class Bar() + Bar() + null.asInstanceOf[Int => Int](2) + } + + class J[T: Manifest] { val arr = Array.empty[T] } + + class F + implicit val ordering: Ordering[F] = ??? + val f: Ordered[F] = new F + + import scala.concurrent.ExecutionContext.Implicits.global + for { + a <- scala.concurrent.Future.successful(1) + b <- scala.concurrent.Future.successful(2) + } println(a) + for { + a <- scala.concurrent.Future.successful(1) + b <- scala.concurrent.Future.successful(2) + if a < b + } yield a + +} diff --git a/semanticdb/input/src/main/scala/example/Traits.scala b/semanticdb/input/src/main/scala/example/Traits.scala new file mode 100644 index 000000000000..fe857fbf7be3 --- /dev/null +++ b/semanticdb/input/src/main/scala/example/Traits.scala @@ -0,0 +1,14 @@ +package example + +trait T { + def x = 2 +} + +sealed trait U +object U { + def u: U = new U {} +} + +class C +trait V { self: C => +} diff --git a/semanticdb/input/src/main/scala/example/Types.scala b/semanticdb/input/src/main/scala/example/Types.scala new file mode 100644 index 000000000000..a22b49115ead --- /dev/null +++ b/semanticdb/input/src/main/scala/example/Types.scala @@ -0,0 +1,115 @@ +package example + +import scala.language.existentials +import scala.language.higherKinds + +class ann[T](x: T) extends scala.annotation.StaticAnnotation +class ann1 extends scala.annotation.StaticAnnotation +class ann2 extends scala.annotation.StaticAnnotation + +class TypB + +class TypC + +class TypP { + class C + class X + val x = new X +} + +class TypT { + class C + class X + val x = new X +} + +object TypTest { + class M { + def m: Int = ??? + } + + trait N { + def n: Int = ??? + } + + class C extends M { + val p = new TypP + val x = p.x + + val typeRef1: C = ??? + val typeRef2: p.C = ??? + val typeRef3: TypT#C = ??? + val typeRef4: List[Int] = ??? + + val singleType1: x.type = ??? + val singleType2: p.x.type = ??? + val Either = scala.util.Either + + val thisType1: this.type = ??? + val thisType2: C.this.type = ??? + + val superType1 = super.m + val superType2 = super[M].m + val superType3 = C.super[M].m + + val compoundType1: { def k: Int } = ??? + val compoundType2: M with N = ??? + val compoundType3: M with N { def k: Int } = ??? + val compoundType4 = new { def k: Int = ??? } + val compoundType5 = new M with N + val compoundType6 = new M with N { def k: Int = ??? } + + val annType1: T @ann(42) = ??? + val annType2: T @ann1 @ann2 = ??? + + val existentialType2: List[_] = ??? + val existentialType3 = Class.forName("foo.Bar") + val existentialType4 = Class.forName("foo.Bar") + + def typeLambda1[M[_]] = ??? + typeLambda1[({ type L[T] = List[T] })#L] + + object ClassInfoType1 + class ClassInfoType2 extends B { def x = 42 } + trait ClassInfoType3[T] + + object MethodType { + def x1: Int = ??? + def x2: Int = ??? + def m3: Int = ??? + def m4(): Int = ??? + def m5(x: Int): Int = ??? + def m6[T](x: T): T = ??? + } + + object ByNameType { + def m1(x: => Int): Int = ??? + } + + case class RepeatedType(s: String*) { + def m1(x: Int*): Int = s.length + } + + object TypeType { + type T1 + def m2[T2 >: C <: C] = ??? + def m3[M3[_]] = ??? + type T4 = C + type T5[U] = U + } + } + + object Literal { + final val int = 1 + final val long = 1L + final val float = 1f + final val double = 2d + final val nil = null + final val char = 'a' + final val string = "a" + final val bool = true + final val unit = () + final val javaEnum = java.nio.file.LinkOption.NOFOLLOW_LINKS + final val clazzOf = classOf[Option[Int]] + } +} diff --git a/semanticdb/input/src/main/scala/example/Vals.scala b/semanticdb/input/src/main/scala/example/Vals.scala new file mode 100644 index 000000000000..64e8026448bc --- /dev/null +++ b/semanticdb/input/src/main/scala/example/Vals.scala @@ -0,0 +1,47 @@ +package example + +abstract class Vals(p: Int, val xp: Int, var yp: Int) { + val xm: Int = ??? + val xam: Int + private[this] val xlm: Int = ??? + lazy val xzm: Int = ??? + private[this] lazy val xzlm: Int = ??? + final val xfm: Int = ??? + implicit val xim: Int = ??? + var ym: Int = ??? + var yam: Int + private[this] var ylm: Int = ??? + // NOTE: lazy not allowed here. Only vals can be lazy + // lazy var xzm: Int = ??? + // private[this] lazy var yzlm: Int = ??? + final var yfm: Int = ??? + implicit var yim: Int = ??? + def m = { + val xl: Int = ??? + lazy val xzl: Int = ??? + // NOTE: local values cannot be final + // final val xfl: Int = ??? + implicit val xil: Int = ??? + var yl: Int = ??? + // NOTE: lazy not allowed here. Only vals can be lazy + // lazy var yzl: Int = ??? + // NOTE: local variables cannot be final + // final var yfl: Int = ??? + implicit var yil: Int = ??? + yl = xl + yl + println(xzl) + yil = xil + yil + } + println(xzlm) + ylm = xlm + ylm +} + +object ValUsages { + val v: Vals = ??? + v.yp = v.xp + v.yp + v.ym = v.xm + v.ym + v.yam = v.xam + v.yam + println(v.xzm) + v.yfm = v.xfm + v.yfm + v.yim = v.xim + v.yim +} diff --git a/semanticdb/input/src/main/scala/example/local-file.scala b/semanticdb/input/src/main/scala/example/local-file.scala new file mode 100644 index 000000000000..ea699bdfff57 --- /dev/null +++ b/semanticdb/input/src/main/scala/example/local-file.scala @@ -0,0 +1,8 @@ +package example + +class `local-file` { + locally { + val local = 42 + local + 4 + } +} diff --git a/semanticdb/src/dotty/semanticdb/Scala.scala b/semanticdb/src/dotty/semanticdb/Scala.scala new file mode 100644 index 000000000000..b4bd983022a5 --- /dev/null +++ b/semanticdb/src/dotty/semanticdb/Scala.scala @@ -0,0 +1,290 @@ +package dotty.semanticdb + +import scala.compat.Platform.EOL +import dotty.semanticdb.Scala.{Descriptor => d} +import dotty.semanticdb.Scala.{Names => n} + +object Scala { + object Symbols { + val None: String = "" + val RootPackage: String = "_root_/" + val EmptyPackage: String = "_empty_/" + def Global(owner: String, desc: Descriptor): String = + if (owner != RootPackage) owner + desc.toString + else desc.toString + def Local(suffix: String): String = { + if (suffix.indexOf("/") == -1 && suffix.indexOf(";") == -1) "local" + suffix + else throw new IllegalArgumentException(suffix) + } + def Multi(symbols: List[String]): String = { + symbols.distinct match { + case List(symbol) => + symbol + case symbols => + val sb = new StringBuilder + symbols.foreach { symbol => + if (!symbol.isMulti) { + sb.append(';') + } + sb.append(symbol) + } + sb.toString() + } + } + } + + implicit class ScalaSymbolOps(symbol: String) { + def isNone: Boolean = + symbol == Symbols.None + def isRootPackage: Boolean = + symbol == Symbols.RootPackage + def isEmptyPackage: Boolean = + symbol == Symbols.EmptyPackage + def isGlobal: Boolean = + !isNone && !isMulti && (symbol.last match { + case '.' | '#' | '/' | ')' | ']' => true + case _ => false + }) + def isLocal: Boolean = + symbol.startsWith("local") + def isMulti: Boolean = + symbol.startsWith(";") + def asMulti: List[String] = { + if (!isMulti) symbol :: Nil + else { + val buf = List.newBuilder[String] + def loop(begin: Int, i: Int): Unit = + if (i >= symbol.length) { + buf += symbol.substring(begin, symbol.length) + } else { + symbol.charAt(i) match { + case ';' => + buf += symbol.substring(begin, i) + loop(i + 1, i + 1) + case '`' => + var j = i + 1 + while (symbol.charAt(j) != '`') j += 1 + loop(begin, j + 1) + case _ => + loop(begin, i + 1) + } + } + loop(1, 1) + buf.result() + } + } + def isTerm: Boolean = + !isNone && !isMulti && symbol.last == '.' + def isType: Boolean = + !isNone && !isMulti && symbol.last == '#' + def isPackage: Boolean = + !isNone && !isMulti && symbol.last == '/' + def isParameter: Boolean = + !isNone && !isMulti && symbol.last == ')' + def isTypeParameter: Boolean = + !isNone && !isMulti && symbol.last == ']' + def ownerChain: List[String] = { + val buf = List.newBuilder[String] + def loop(symbol: String): Unit = { + if (!symbol.isNone) { + loop(symbol.owner) + buf += symbol + } + } + loop(symbol) + buf.result + } + def owner: String = { + if (isGlobal) { + if (isRootPackage) Symbols.None + else { + val rest = DescriptorParser(symbol)._2 + if (rest.nonEmpty) rest + else Symbols.RootPackage + } + } else { + Symbols.None + } + } + def desc: Descriptor = { + if (isGlobal) { + DescriptorParser(symbol)._1 + } else { + d.None + } + } + } + + sealed trait Descriptor { + def isNone: Boolean = this == d.None + def isTerm: Boolean = this.isInstanceOf[d.Term] + def isMethod: Boolean = this.isInstanceOf[d.Method] + def isType: Boolean = this.isInstanceOf[d.Type] + def isPackage: Boolean = this.isInstanceOf[d.Package] + def isParameter: Boolean = this.isInstanceOf[d.Parameter] + def isTypeParameter: Boolean = this.isInstanceOf[d.TypeParameter] + def value: String + def name: n.Name = { + this match { + case d.None => n.TermName(value) + case d.Term(value) => n.TermName(value) + case d.Method(value, disambiguator) => n.TermName(value) + case d.Type(value) => n.TypeName(value) + case d.Package(value) => n.TermName(value) + case d.Parameter(value) => n.TermName(value) + case d.TypeParameter(value) => n.TypeName(value) + } + } + override def toString: String = { + this match { + case d.None => sys.error("unsupported descriptor") + case d.Term(value) => s"${n.encode(value)}." + case d.Method(value, disambiguator) => s"${n.encode(value)}${disambiguator}." + case d.Type(value) => s"${n.encode(value)}#" + case d.Package(value) => s"${n.encode(value)}/" + case d.Parameter(value) => s"(${n.encode(value)})" + case d.TypeParameter(value) => s"[${n.encode(value)}]" + } + } + } + object Descriptor { + final case object None extends Descriptor { def value: String = "" } + final case class Term(value: String) extends Descriptor + final case class Method(value: String, disambiguator: String) extends Descriptor + final case class Type(value: String) extends Descriptor + final case class Package(value: String) extends Descriptor + final case class Parameter(value: String) extends Descriptor + final case class TypeParameter(value: String) extends Descriptor + } + + object Names { + // NOTE: This trait is defined inside Names to support the idiom of importing + // scala.meta.internal.semanticdb.Scala._ and not being afraid of name conflicts. + sealed trait Name { + def value: String + override def toString: String = value + } + final case class TermName(value: String) extends Name + final case class TypeName(value: String) extends Name + + val RootPackage: TermName = TermName("_root_") + val EmptyPackage: TermName = TermName("_empty_") + val PackageObject: TermName = TermName("package") + val Constructor: TermName = TermName("") + + private[semanticdb] def encode(value: String): String = { + if (value == "") { + "``" + } else { + val (start, parts) = (value.head, value.tail) + val isStartOk = Character.isJavaIdentifierStart(start) + val isPartsOk = parts.forall(Character.isJavaIdentifierPart) + if (isStartOk && isPartsOk) value + else "`" + value + "`" + } + } + } + + object DisplayNames { + val RootPackage: String = "_root_" + val EmptyPackage: String = "_empty_" + val Constructor: String = "" + val Anonymous: String = "_" + } + + private class DescriptorParser(s: String) { + var i = s.length + def fail() = { + val message = "invalid symbol format" + val caret = " " * i + "^" + sys.error(s"$message$EOL$s$EOL$caret") + } + + val BOF = '\u0000' + val EOF = '\u001A' + var currChar = EOF + def readChar(): Char = { + if (i <= 0) { + if (i == 0) { + i -= 1 + currChar = BOF + currChar + } else { + fail() + } + } else { + i -= 1 + currChar = s(i) + currChar + } + } + + def parseValue(): String = { + if (currChar == '`') { + val end = i + while (readChar() != '`') {} + readChar() + s.substring(i + 2, end) + } else { + val end = i + 1 + if (!Character.isJavaIdentifierPart(currChar)) fail() + while (Character.isJavaIdentifierPart(readChar()) && currChar != BOF) {} + s.substring(i + 1, end) + } + } + + def parseDisambiguator(): String = { + val end = i + 1 + if (currChar != ')') fail() + while (readChar() != '(') {} + readChar() + s.substring(i + 1, end) + } + + def parseDescriptor(): Descriptor = { + if (currChar == '.') { + readChar() + if (currChar == ')') { + val disambiguator = parseDisambiguator() + val value = parseValue() + d.Method(value, disambiguator) + } else { + d.Term(parseValue()) + } + } else if (currChar == '#') { + readChar() + d.Type(parseValue()) + } else if (currChar == '/') { + readChar() + d.Package(parseValue()) + } else if (currChar == ')') { + readChar() + val value = parseValue() + if (currChar != '(') fail() + else readChar() + d.Parameter(value) + } else if (currChar == ']') { + readChar() + val value = parseValue() + if (currChar != '[') fail() + else readChar() + d.TypeParameter(value) + } else { + fail() + } + } + + def entryPoint(): (Descriptor, String) = { + readChar() + val desc = parseDescriptor() + (desc, s.substring(0, i + 1)) + } + } + + private[semanticdb] object DescriptorParser { + def apply(symbol: String): (Descriptor, String) = { + val parser = new DescriptorParser(symbol) + parser.entryPoint() + } + } +} diff --git a/semanticdb/src/dotty/semanticdb/SemanticdbConsumer.scala b/semanticdb/src/dotty/semanticdb/SemanticdbConsumer.scala index 2f7b543cde52..014c29d51030 100644 --- a/semanticdb/src/dotty/semanticdb/SemanticdbConsumer.scala +++ b/semanticdb/src/dotty/semanticdb/SemanticdbConsumer.scala @@ -3,21 +3,289 @@ package dotty.semanticdb import scala.tasty.Reflection import scala.tasty.file.TastyConsumer +import dotty.tools.dotc.tastyreflect +import scala.collection.mutable.HashMap +import scala.collection.mutable.Set +import scala.meta.internal.{semanticdb => s} +import dotty.semanticdb.Scala.{Descriptor => d} +import dotty.semanticdb.Scala._ + class SemanticdbConsumer extends TastyConsumer { + var stack: List[String] = Nil + + val semantic: s.TextDocument = s.TextDocument() + var occurrences: Seq[s.SymbolOccurrence] = Seq() + + def toSemanticdb(text: String): s.TextDocument = { + s.TextDocument(text = text, occurrences = occurrences) + } + val package_definitions: Set[String] = Set() final def apply(reflect: Reflection)(root: reflect.Tree): Unit = { import reflect._ + + val symbolsCache: HashMap[Symbol, String] = HashMap() + + object ChildTraverser extends TreeTraverser { + var children: List[Tree] = Nil + override def traverseTree(tree: Tree)(implicit ctx: Context): Unit = + children = tree :: children + override def traversePattern(pattern: Pattern)( + implicit ctx: Context): Unit = () + override def traverseTypeTree(tree: TypeOrBoundsTree)( + implicit ctx: Context): Unit = () + override def traverseCaseDef(tree: CaseDef)(implicit ctx: Context): Unit = + () + override def traverseTypeCaseDef(tree: TypeCaseDef)(implicit ctx: Context): Unit = + () + + def getChildren(tree: Tree)(implicit ctx: Context): List[Tree] = { + children = Nil + traverseTreeChildren(tree)(ctx) + return children + } + } + object Traverser extends TreeTraverser { + implicit class TreeExtender(tree: Tree) { + def isUserCreated: Boolean = { + val children: List[Position] = + ChildTraverser.getChildren(tree)(reflect.rootContext).map(_.pos) + return !((tree.pos.exists && tree.pos.start == tree.pos.end && children == Nil) || children + .exists(_ == tree.pos)) + } + } - override def traverseTree(tree: Tree)(implicit ctx: Context): Unit = tree match { - case IsDefinition(tree) => - println(tree.name) - super.traverseTree(tree) - case tree => - super.traverseTree(tree) + implicit class TypeTreeExtender(tree: TypeTree) { + def isUserCreated: Boolean = { + return !(tree.pos.exists && tree.pos.start == tree.pos.end) + } + } + + implicit class SymbolExtender(symbol: Symbol) { + def isTypeParameter: Boolean = symbol match { + case IsTypeSymbol(_) => symbol.flags.isParam + case _ => false + } + + def isType: Boolean = symbol match { + case IsTypeSymbol(_) => true + case _ => false + } + + def isMethod: Boolean = symbol match { + case IsDefSymbol(_) => true + case _ => false + } + + def isPackage: Boolean = symbol match { + case IsPackageSymbol(_) => true + case _ => false + } + + def isObject: Boolean = symbol.flags.isObject + + def isTrait: Boolean = symbol.flags.isTrait + + def isValueParameter: Boolean = symbol.flags.isParam + + // TODO : implement it + def isJavaClass: Boolean = false + } + + def resolveClass(symbol: ClassSymbol): Symbol = + (symbol.companionClass, symbol.companionModule) match { + case (_, Some(module)) if symbol.flags.isObject => module + case (Some(c), _) => c + case _ => symbol + } + + def disimbiguate(symbol_path: String, symbol: Symbol): String = { + val symbolcl = resolveClass(symbol.owner.asClass) + val methods = symbolcl.asClass.method(symbol.name) + val (methods_count, method_pos) = + methods.foldLeft((0, -1))((x: Tuple2[Int, Int], m: Symbol) => { + if (m == symbol) + (x._1 + 1, x._1) + else + (x._1 + 1, x._2) + }) + val real_pos = methods_count - method_pos - 1 + + if (real_pos == 0) { + "()" + } else { + "(+" + real_pos + ")" + } + } + + def iterateParent(symbol: Symbol): String = { + if (symbolsCache.contains(symbol)) { + return symbolsCache(symbol) + } else { + val out_symbol_path = + if (symbol.name == "" || symbol.name == "") then { + // TODO had a "NoDenotation" test to avoid + // relying on the name itself + "" + } else { + val previous_symbol = iterateParent(symbol.owner) + val next_atom = + if (symbol.isPackage) { + d.Package(symbol.name) + } else if (symbol.isObject) { + d.Term(resolveClass(symbol.asClass).name) + } else if (symbol.isMethod) { + d.Method(symbol.name, + disimbiguate(previous_symbol + symbol.name, symbol)) + } else if (symbol.isValueParameter) { + d.Parameter(symbol.name) + } else if (symbol.isTypeParameter) { + d.TypeParameter(symbol.name) + } else if (symbol.isType || symbol.isTrait) { + d.Type(symbol.name) + } else { + d.Term(symbol.name) + } + + Symbols.Global(previous_symbol, next_atom) + } + symbolsCache += (symbol -> out_symbol_path) + out_symbol_path + } + } + + def addOccurence(symbol: Symbol, + type_symbol: s.SymbolOccurrence.Role, + range: s.Range): Unit = { + val symbol_path = iterateParent(symbol) + if (symbol_path == "") return + + occurrences = + occurrences :+ + s.SymbolOccurrence( + Some(range), + symbol_path, + type_symbol + ) + + } + + def addOccurenceTree(tree: Tree, + type_symbol: s.SymbolOccurrence.Role, + range: s.Range, + force_add: Boolean = false): Unit = { + if (tree.isUserCreated || force_add) { + addOccurence(tree.symbol, type_symbol, range) + } + } + def addOccurenceTypeTree(typetree: TypeTree, + type_symbol: s.SymbolOccurrence.Role, + range: s.Range): Unit = { + if (typetree.isUserCreated) { + addOccurence(typetree.symbol, type_symbol, range) + } + } + + def range(tree: Tree, pos: Position, name: String): s.Range = { + val offset = tree match { + case IsPackageClause(tree) => "package ".length + case _ => 0 + } + + val range_end_column = + if (name == "") { + pos.endColumn + } else { + pos.startColumn + name.length + } + + s.Range(pos.startLine, + pos.startColumn + offset, + pos.startLine, + range_end_column + offset) + } + + def rangeExclude(range: Position, exclude: Position): s.Range = { + def max(a: Int, b: Int): Int = { if (a > b) a else b } + return s.Range(max(range.startLine, exclude.startLine), + max(range.startColumn, exclude.startColumn) + 1, + range.endLine, + range.endColumn) + } + + def typetreeSymbol(tree: Tree, typetree: TypeTree): Unit = + typetree match { + case TypeTree.Synthetic => () + case _ => + addOccurenceTypeTree( + typetree, + s.SymbolOccurrence.Role.REFERENCE, + range(tree, typetree.pos, typetree.symbol.name)) + } + + override def traverseTree(tree: Tree)(implicit ctx: Context): Unit = { + //println(tree.pos.startColumn, tree.symbol.name, tree.pos.endColumn) + tree match { + case IsDefinition(body) => { + tree match { + case DefDef(name, _, _, typetree, _) => + typetreeSymbol(tree, typetree) + case ValDef(_, typetree, _) => + typetreeSymbol(tree, typetree) + case _ => () + } + + val range_symbol = range(tree, tree.symbol.pos, tree.symbol.name) + if (tree.symbol.name == "" && !tree.isUserCreated) { + val range_symbol2 = s.Range(range_symbol.startLine, + range_symbol.startCharacter - 4, + range_symbol.endLine, + range_symbol.endCharacter - 4) + addOccurenceTree(tree, + s.SymbolOccurrence.Role.DEFINITION, + range_symbol2, + true) + + } else { + addOccurenceTree(tree, + s.SymbolOccurrence.Role.DEFINITION, + range_symbol) + } + super.traverseTree(body) + } + + case Term.Select(qualifier, _, _) => { + val range = rangeExclude(tree.pos, qualifier.pos) + addOccurenceTree(tree, s.SymbolOccurrence.Role.REFERENCE, range) + super.traverseTree(tree) + } + + case Term.Ident(_) => { + // To avoid adding the identifier of the package symbol + if (tree.symbol.owner.name != "") { + addOccurenceTree(tree, + s.SymbolOccurrence.Role.REFERENCE, + range(tree, tree.pos, tree.symbol.name)) + } + super.traverseTree(tree) + } + case PackageClause(_) => + if (!package_definitions(tree.symbol.name)) { + addOccurenceTree(tree, + s.SymbolOccurrence.Role.REFERENCE, + range(tree, tree.pos, tree.symbol.name)) + package_definitions += tree.symbol.name + } + super.traverseTree(tree) + + case tree => + super.traverseTree(tree) + } } } + Traverser.traverseTree(root)(reflect.rootContext) } diff --git a/semanticdb/test/dotty/semanticdb/Semanticdbs.scala b/semanticdb/test/dotty/semanticdb/Semanticdbs.scala index 7c766c6e7380..d626558f2adf 100644 --- a/semanticdb/test/dotty/semanticdb/Semanticdbs.scala +++ b/semanticdb/test/dotty/semanticdb/Semanticdbs.scala @@ -7,7 +7,7 @@ import scala.collection.JavaConverters._ import dotty.tools.dotc.util.SourceFile object Semanticdbs { - + private val buildSubFolder = "semanticdb/input/" /** * Utility to load SemanticDB for Scala source files. * @@ -17,7 +17,7 @@ object Semanticdbs { * if you only care about reading SemanticDB files from a single project. */ class Loader(sourceroot: Path, classpath: List[Path]) { - private val META_INF = Paths.get("META-INF", "semanticdb") + private val META_INF = Paths.get("META-INF", "semanticdb").resolve(buildSubFolder) private val classLoader = new java.net.URLClassLoader(classpath.map(_.toUri.toURL).toArray) /** Returns a SemanticDB for a single Scala source file, if any. The path must be absolute. */ def resolve(scalaAbsolutePath: Path): Option[s.TextDocument] = { @@ -43,7 +43,7 @@ object Semanticdbs { scalaRelativePath: Path, semanticdbAbsolutePath: Path ): s.TextDocument = { - val reluri = scalaRelativePath.iterator.asScala.mkString("/") + val reluri = buildSubFolder + scalaRelativePath.iterator.asScala.mkString("/") val sdocs = parseTextDocuments(semanticdbAbsolutePath) sdocs.documents.find(_.uri == reluri) match { case None => throw new NoSuchElementException(reluri) diff --git a/semanticdb/test/dotty/semanticdb/Tests.scala b/semanticdb/test/dotty/semanticdb/Tests.scala index 980a78d949d9..82509096c5c6 100644 --- a/semanticdb/test/dotty/semanticdb/Tests.scala +++ b/semanticdb/test/dotty/semanticdb/Tests.scala @@ -2,37 +2,145 @@ package dotty.semanticdb import scala.tasty.Reflection import scala.tasty.file._ +import scala.collection.mutable.HashMap import org.junit.Test import org.junit.Assert._ import java.nio.file._ import scala.meta.internal.{semanticdb => s} import scala.collection.JavaConverters._ +import java.io.File +import scala.tasty.Reflection +import scala.tasty.file.TastyConsumer +import java.lang.reflect.InvocationTargetException + +class TastyInspecter extends TastyConsumer { + var source_path: Option[String] = None + final def apply(reflect: Reflection)(root: reflect.Tree): Unit = { + import reflect._ + object ChildTraverser extends TreeTraverser { + override def traverseTree(tree: Tree)(implicit ctx: Context): Unit = + tree match { + case IsClassDef(cdef) => { + cdef.symbol.annots.foreach { annot => + annot match { + case Term.Apply(Term.Select(Term.New(t), _, _), + List(Term.Literal(Constant.String(path)))) + if t.symbol.name == "SourceFile" => + // we found the path to a file. In this case, we do not need to + // continue traversing the tree + source_path = Some(path) + case x => super.traverseTree(tree) + } + true + } + } + case _ => { + if (source_path == None) + super.traverseTree(tree) + else + () + } + } + } + ChildTraverser.traverseTree(root)(reflect.rootContext) + } +} class Tests { // TODO: update scala-0.10 on version change (or resolve automatically) - final def tastyClassDirectory = "out/bootstrap/dotty-semanticdb/scala-0.11/test-classes" + final def tastyClassDirectory = + "out/bootstrap/dotty-semanticdb/scala-0.11/test-classes" val sourceroot = Paths.get("semanticdb", "input").toAbsolutePath val sourceDirectory = sourceroot.resolve("src/main/scala") val semanticdbClassDirectory = sourceroot.resolve("target/scala-2.12/classes") - val semanticdbLoader = new Semanticdbs.Loader(sourceroot, List(semanticdbClassDirectory)) + val semanticdbLoader = + new Semanticdbs.Loader(sourceroot, List(semanticdbClassDirectory)) + + val source_to_tasty = + getTastyFiles( + Paths.get("out", "bootstrap", "dotty-semanticdb/").toAbsolutePath) + /** Returns the SemanticDB for this Scala source file. */ def getScalacSemanticdb(scalaFile: Path): s.TextDocument = { semanticdbLoader.resolve(scalaFile).get } - /** TODO: Produce semanticdb from TASTy for this Scala source file. */ + /** List all tasty files occuring in the folder f or one of its subfolders */ + def recursiveListFiles(f: File): Array[File] = { + val pattern = ".*test-classes/example.*\\.tasty".r + val files = f.listFiles + val folders = files.filter(_.isDirectory) + val tastyfiles = files.filter(_.toString match { + case pattern(x: _*) => true + case _ => false + }) + tastyfiles ++ folders.flatMap(recursiveListFiles) + } + + /** Returns a mapping from *.scala file to a list of tasty files. */ + def getTastyFiles(artifactsPath: Path): HashMap[String, List[Path]] = { + val source_to_tasty: HashMap[String, List[Path]] = HashMap() + val tastyfiles = recursiveListFiles(artifactsPath.toFile()) + recursiveListFiles(artifactsPath.toFile()).map(tasty_path => { + val (classpath, classname) = getClasspathClassname(tasty_path.toPath()) + // We add an exception here to avoid crashing if we encountered + // a bad tasty file + try { + val inspecter = new TastyInspecter + ConsumeTasty(classpath, classname :: Nil, inspecter) + inspecter.source_path.foreach( + source => + source_to_tasty += + (source -> (tasty_path + .toPath() :: source_to_tasty.getOrElse(source, Nil)))) + } catch { + case _: InvocationTargetException => println(tasty_path) + } + }) + source_to_tasty + } + + /** Infers a tuple (class path, class name) from a given path */ + def getClasspathClassname(file: Path): (String, String) = { + val pat = """(.*)\..*""".r + val classpath = file.getParent().getParent().toString() + val modulename = file.getParent().getFileName().toString() + val sourcename = + file.toFile().getName().toString() match { + case pat(name) => name + case _ => "" + } + return (classpath, modulename + "." + sourcename) + } + def getTastySemanticdb(scalaFile: Path): s.TextDocument = { - ??? + val scalac = getScalacSemanticdb(scalaFile) + + val tasty_files = source_to_tasty.getOrElse( + sourceDirectory.resolve(scalaFile).toString, + Nil) + + val tasty_classes = tasty_files.map(getClasspathClassname) + // If we have more than one classpath then something went wrong + if (tasty_classes.groupBy((a, _) => a).size != 1) { + scalac + } else { + val (classpaths, classnames) = tasty_classes.unzip + val sdbconsumer = new SemanticdbConsumer + + val _ = ConsumeTasty(classpaths.head, classnames, sdbconsumer) + sdbconsumer.toSemanticdb(scalac.text) + } } /** Fails the test if the s.TextDocument from tasty and semanticdb-scalac are not the same. */ def checkFile(filename: String): Unit = { val path = sourceDirectory.resolve(filename) val scalac = getScalacSemanticdb(path) - val tasty = s.TextDocument(text = scalac.text) // TODO: replace with `getTastySemanticdb(path)` + val tasty = getTastySemanticdb(path) val obtained = Semanticdbs.printTextDocument(tasty) val expected = Semanticdbs.printTextDocument(scalac) assertNoDiff(obtained, expected) @@ -49,9 +157,16 @@ class Tests { val diff = if (patch.getDeltas.isEmpty) "" else { - difflib.DiffUtils.generateUnifiedDiff( - "tasty", "scala2", obtainedLines, patch, 1 - ).asScala.mkString("\n") + difflib.DiffUtils + .generateUnifiedDiff( + "tasty", + "scala2", + obtainedLines, + patch, + 1 + ) + .asScala + .mkString("\n") } if (!diff.isEmpty) { fail("\n" + diff)