diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/FlagsOpsImpl.scala b/compiler/src/dotty/tools/dotc/tastyreflect/FlagsOpsImpl.scala index d71935e79a50..cb65129d3d25 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/FlagsOpsImpl.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/FlagsOpsImpl.scala @@ -43,6 +43,11 @@ trait FlagsOpsImpl extends scala.tasty.reflect.FlagsOps with CoreImpl { def StableRealizable: Flags = core.Flags.StableRealizable def Param: Flags = core.Flags.Param def ParamAccessor: Flags = core.Flags.ParamAccessor + def Enum: Flags = core.Flags.Enum + def ModuleClass: Flags = core.Flags.ModuleClass + def PrivateLocal: Flags = core.Flags.PrivateLocal + def Package: Flags = core.Flags.Package + def ImplClass: Flags = core.Flags.ImplClass } } diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/ImportSelectorOpsImpl.scala b/compiler/src/dotty/tools/dotc/tastyreflect/ImportSelectorOpsImpl.scala index fc71e87f682e..8300b213d93f 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/ImportSelectorOpsImpl.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/ImportSelectorOpsImpl.scala @@ -2,6 +2,7 @@ package dotty.tools.dotc.tastyreflect import dotty.tools.dotc.ast.{Trees, untpd} import dotty.tools.dotc.core.StdNames.nme +import dotty.tools.dotc.core.Decorators._ trait ImportSelectorOpsImpl extends scala.tasty.reflect.ImportSelectorOps with CoreImpl { diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/PatternOpsImpl.scala b/compiler/src/dotty/tools/dotc/tastyreflect/PatternOpsImpl.scala index 88d6df9723dd..764a757e838b 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/PatternOpsImpl.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/PatternOpsImpl.scala @@ -36,6 +36,7 @@ trait PatternOpsImpl extends scala.tasty.reflect.PatternOps with CoreImpl { def PatternDeco(pattern: Pattern): PatternAPI = new PatternAPI { def pos(implicit ctx: Context): Position = pattern.sourcePos def tpe(implicit ctx: Context): Type = pattern.tpe.stripTypeVar + def symbol(implicit ctx: Context): Symbol = pattern.symbol } object Pattern extends PatternModule { diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/SymbolOpsImpl.scala b/compiler/src/dotty/tools/dotc/tastyreflect/SymbolOpsImpl.scala index b096c7d83d5a..1e2600a3d86c 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/SymbolOpsImpl.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/SymbolOpsImpl.scala @@ -30,6 +30,14 @@ trait SymbolOpsImpl extends scala.tasty.reflect.SymbolOps with CoreImpl { def owner(implicit ctx: Context): Symbol = symbol.owner + def isLocalDummy(implicit ctx: Context): Boolean = symbol.isLocalDummy + def isRefinementClass(implicit ctx: Context): Boolean = symbol.isRefinementClass + def isAliasType(implicit ctx: Context): Boolean = symbol.isAliasType + def isAnonymousClass(implicit ctx: Context): Boolean = symbol.isAnonymousClass + def isAnonymousFunction(implicit ctx: Context): Boolean = symbol.isAnonymousFunction + def isAbstractType(implicit ctx: Context): Boolean = symbol.isAbstractType + def isClassConstructor(implicit ctx: Context): Boolean = symbol.isClassConstructor + def localContext(implicit ctx: Context): Context = { if (symbol.exists) ctx.withOwner(symbol) else ctx @@ -167,6 +175,11 @@ trait SymbolOpsImpl extends scala.tasty.reflect.SymbolOps with CoreImpl { if (sym.exists) Some(sym.asTerm) else None } + def moduleClass(implicit ctx: Context): Option[Symbol] = { + val sym = symbol.moduleClass + if (sym.exists) Some(sym.asTerm) else None + } + private def isField(sym: Symbol)(implicit ctx: Context): Boolean = sym.isTerm && !sym.is(Flags.Method) } diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/TypeOrBoundsOpsImpl.scala b/compiler/src/dotty/tools/dotc/tastyreflect/TypeOrBoundsOpsImpl.scala index 780040bbfae1..f375733f5e00 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/TypeOrBoundsOpsImpl.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/TypeOrBoundsOpsImpl.scala @@ -21,6 +21,8 @@ trait TypeOrBoundsOpsImpl extends scala.tasty.reflect.TypeOrBoundsOps with CoreI def classSymbol(implicit ctx: Context): Option[ClassSymbol] = if (tpe.classSymbol.exists) Some(tpe.classSymbol.asClass) else None + + def typeSymbol(implicit ctx: Context): Symbol = tpe.typeSymbol } def ConstantTypeDeco(x: ConstantType): Type.ConstantTypeAPI = new Type.ConstantTypeAPI { diff --git a/library/src/scala/tasty/reflect/FlagsOps.scala b/library/src/scala/tasty/reflect/FlagsOps.scala index eafef4cf3d01..6eff5e459a4f 100644 --- a/library/src/scala/tasty/reflect/FlagsOps.scala +++ b/library/src/scala/tasty/reflect/FlagsOps.scala @@ -101,6 +101,21 @@ trait FlagsOps extends Core { /** Is this symbol a parameter accessor */ def ParamAccessor: Flags + + /** Is this symbol an enum */ + def Enum: Flags + + /** Is this symbol a module class */ + def ModuleClass: Flags + + /** Is this symbol labeled private[this] */ + def PrivateLocal: Flags + + /** Is this symbol a package */ + def Package: Flags + + /** Is this symbol an implementation class of a Scala2 trait */ + def ImplClass: Flags } } diff --git a/library/src/scala/tasty/reflect/PatternOps.scala b/library/src/scala/tasty/reflect/PatternOps.scala index 0ad4d7307dd0..dd43db40f035 100644 --- a/library/src/scala/tasty/reflect/PatternOps.scala +++ b/library/src/scala/tasty/reflect/PatternOps.scala @@ -14,6 +14,8 @@ trait PatternOps extends Core { def pos(implicit ctx: Context): Position def tpe(implicit ctx: Context): Type + + def symbol(implicit ctx: Context): Symbol } implicit def PatternDeco(pattern: Pattern): PatternAPI diff --git a/library/src/scala/tasty/reflect/Printers.scala b/library/src/scala/tasty/reflect/Printers.scala index 000eff2a8ddc..0432c4d83056 100644 --- a/library/src/scala/tasty/reflect/Printers.scala +++ b/library/src/scala/tasty/reflect/Printers.scala @@ -129,6 +129,11 @@ trait Printers if (flags.is(Flags.StableRealizable)) flagList += "Flags.StableRealizable" if (flags.is(Flags.Param)) flagList += "Flags.Param" if (flags.is(Flags.ParamAccessor)) flagList += "Flags.ParamAccessor" + if (flags.is(Flags.Enum)) flagList += "Flags.Enum" + if (flags.is(Flags.ModuleClass)) flagList += "Flags.ModuleClass" + if (flags.is(Flags.PrivateLocal)) flagList += "Flags.PrivateLocal" + if (flags.is(Flags.Package)) flagList += "Flags.Package" + if (flags.is(Flags.ImplClass)) flagList += "Flags.ImplClass" flagList.result().mkString(" | ") } @@ -504,6 +509,11 @@ trait Printers if (flags.is(Flags.StableRealizable)) flagList += "stableRealizable" if (flags.is(Flags.Param)) flagList += "param" if (flags.is(Flags.ParamAccessor)) flagList += "paramAccessor" + if (flags.is(Flags.Enum)) flagList += "enum" + if (flags.is(Flags.ModuleClass)) flagList += "moduleClass" + if (flags.is(Flags.PrivateLocal)) flagList += "private[this]" + if (flags.is(Flags.Package)) flagList += "package" + if (flags.is(Flags.ImplClass)) flagList += "implClass" flagList.result().mkString("/*", " ", "*/") } diff --git a/library/src/scala/tasty/reflect/SymbolOps.scala b/library/src/scala/tasty/reflect/SymbolOps.scala index 2f1af7b87bea..6d3b2439cbf4 100644 --- a/library/src/scala/tasty/reflect/SymbolOps.scala +++ b/library/src/scala/tasty/reflect/SymbolOps.scala @@ -14,6 +14,14 @@ trait SymbolOps extends Core { /** Flags of this symbol */ def flags(implicit ctx: Context): Flags + def isLocalDummy(implicit ctx: Context): Boolean + def isRefinementClass(implicit ctx: Context): Boolean + def isAliasType(implicit ctx: Context): Boolean + def isAnonymousClass(implicit ctx: Context): Boolean + def isAnonymousFunction(implicit ctx: Context): Boolean + def isAbstractType(implicit ctx: Context): Boolean + def isClassConstructor(implicit ctx: Context): Boolean + /** This symbol is private within the resulting type. */ def privateWithin(implicit ctx: Context): Option[Type] @@ -26,6 +34,7 @@ trait SymbolOps extends Core { /** The full name of this symbol up to the root package. */ def fullName(implicit ctx: Context): String + /** The position of this symbol */ def pos(implicit ctx: Context): Position def localContext(implicit ctx: Context): Context @@ -111,6 +120,7 @@ trait SymbolOps extends Core { /** The symbol of the companion module */ def companionModule(implicit ctx: Context): Option[ValSymbol] + def moduleClass(implicit ctx: Context): Option[Symbol] } implicit def ClassSymbolDeco(symbol: ClassSymbol): ClassSymbolAPI @@ -170,7 +180,7 @@ trait SymbolOps extends Core { } trait BindSymbolAPI { - /** Bind pattern of this defintion. */ + /** Bind pattern of this definition. */ def tree(implicit ctx: Context): Bind } implicit def BindSymbolDeco(symbol: BindSymbol): BindSymbolAPI diff --git a/library/src/scala/tasty/reflect/TypeOrBoundsOps.scala b/library/src/scala/tasty/reflect/TypeOrBoundsOps.scala index 2e5a90ddf619..1063d4345e25 100644 --- a/library/src/scala/tasty/reflect/TypeOrBoundsOps.scala +++ b/library/src/scala/tasty/reflect/TypeOrBoundsOps.scala @@ -54,6 +54,7 @@ trait TypeOrBoundsOps extends Core { def <:<(other: Type)(implicit ctx: Context): Boolean def widen(implicit ctx: Context): Type def classSymbol(implicit ctx: Context): Option[ClassSymbol] + def typeSymbol(implicit ctx: Context): Symbol } val IsType: IsTypeModule diff --git a/project/Build.scala b/project/Build.scala index 7bbc21f1f01c..3c82b9669d98 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -378,6 +378,7 @@ object Build { baseDirectory in (Compile, run) := baseDirectory.value / "..", baseDirectory in Test := baseDirectory.value / "..", unmanagedSourceDirectories in Test += baseDirectory.value / "input" / "src" / "main" / "scala", + scalacOptions in Test ++= Seq("-Yno-inline"), libraryDependencies ++= List( ("org.scalameta" %% "semanticdb" % "4.0.0").withDottyCompat(scalaVersion.value), "com.novocode" % "junit-interface" % "0.11", diff --git a/semanticdb/input/src/main/scala/example/Access.scala b/semanticdb/input/src/main/scala/example/Access.scala index aee3754da66b..eba966f117e4 100644 --- a/semanticdb/input/src/main/scala/example/Access.scala +++ b/semanticdb/input/src/main/scala/example/Access.scala @@ -8,4 +8,20 @@ class Access { protected[this] def m5 = ??? protected[example] def m6 = ??? def m7 = ??? + + private val mv1 = ??? + private[this] val mv2 = ??? + private[Access] val mv3 = ??? + protected val mv4 = ??? + protected[this] val mv5 = ??? + protected[example] val mv6 = ??? + val mv7 = ??? + + private var mr1 = ??? + private[this] var mr2 = ??? + private[Access] var mr3 = ??? + protected var mr4 = ??? + protected[this] var mr5 = ??? + protected[example] var mr6 = ??? + var mr7 = ??? } diff --git a/semanticdb/input/src/main/scala/example/Advanced.scala b/semanticdb/input/src/main/scala/example/Advanced.scala index fdee5eccad19..b9aa9c0c8bbc 100644 --- a/semanticdb/input/src/main/scala/example/Advanced.scala +++ b/semanticdb/input/src/main/scala/example/Advanced.scala @@ -1,7 +1,6 @@ package example -import scala.language.existentials -import scala.language.higherKinds +import scala.language.{existentials, higherKinds=>h} import scala.language.reflectiveCalls class AdvC[T] { diff --git a/semanticdb/input/src/main/scala/example/Anonymous.scala b/semanticdb/input/src/main/scala/example/Anonymous.scala index aae065fa7ae3..e807429f536c 100644 --- a/semanticdb/input/src/main/scala/example/Anonymous.scala +++ b/semanticdb/input/src/main/scala/example/Anonymous.scala @@ -15,4 +15,4 @@ class Anonymous { trait Foo var x = new Foo {} -} +} \ No newline at end of file diff --git a/semanticdb/input/src/main/scala/example/Apply.scala b/semanticdb/input/src/main/scala/example/Apply.scala new file mode 100644 index 000000000000..72f84468ba31 --- /dev/null +++ b/semanticdb/input/src/main/scala/example/Apply.scala @@ -0,0 +1,9 @@ +package example + +class TestApply { + object Foo { + def apply(x: Int) : Int = x + } + val z = Foo(1) + val y = Foo.apply(1) +} \ No newline at end of file diff --git a/semanticdb/input/src/main/scala/example/BinaryOp.scala b/semanticdb/input/src/main/scala/example/BinaryOp.scala new file mode 100644 index 000000000000..054c87f2b62f --- /dev/null +++ b/semanticdb/input/src/main/scala/example/BinaryOp.scala @@ -0,0 +1,7 @@ +package example + +class BinaryOp { + val y = 1 #:: 2 #:: Stream.empty[Int] + val x = 1 :: 2 :: 3 :: Nil + val z = 1 + 2 +} \ No newline at end of file diff --git a/semanticdb/input/src/main/scala/example/Case.scala b/semanticdb/input/src/main/scala/example/Case.scala new file mode 100644 index 000000000000..6aebcb750088 --- /dev/null +++ b/semanticdb/input/src/main/scala/example/Case.scala @@ -0,0 +1,12 @@ +package example + +class CaseTest { + def foo (x: Option[Int]) : Int = + x match { + case y @ Some(x) => x + case None => 0 + } +} + +case class CaseClass(x: Int) +case object CaseObject \ No newline at end of file diff --git a/semanticdb/input/src/main/scala/example/Classes.scala b/semanticdb/input/src/main/scala/example/Classes.scala index b82f2c030ff0..1d0770e805f3 100644 --- a/semanticdb/input/src/main/scala/example/Classes.scala +++ b/semanticdb/input/src/main/scala/example/Classes.scala @@ -1,14 +1,26 @@ package example +class CDep[X] +class CDependenat[X /* This is a comment /* and a nested comment +*/*/](var x1: CDep[X]) +class CVarArg(var x1 : Int) + + +class CDefaultWrapper { + val glob = 3 + class Cdefault(val x1: Int = glob) +} class C1(val x1: Int) extends AnyVal class C2(val x2: Int) extends AnyVal object C2 -case class C3(x: Int) +case class C3[Y ](x: Int) case class C4(x: Int) -object C4 +object C4 { + val foo: Int = 4 +} object M { implicit class C5(x: Int) @@ -30,4 +42,15 @@ object N { val local = 2 local + 2 } -} + + val otheranon = { + var a = 1 + a + } + + val lastanon = { + val a = new CVarArg(4) + a.x1 = 8 + a.x1 + } +} \ No newline at end of file diff --git a/semanticdb/input/src/main/scala/example/DependantModule.scala b/semanticdb/input/src/main/scala/example/DependantModule.scala new file mode 100644 index 000000000000..104a0579fec0 --- /dev/null +++ b/semanticdb/input/src/main/scala/example/DependantModule.scala @@ -0,0 +1,9 @@ +package example + +class DepTemp { + +} + +abstract class DepAdvD[CC[X[C] <: B], X[Z], C] extends DepTemp { +val foo: List[Option[Option[X[C]]]] +} \ No newline at end of file diff --git a/semanticdb/input/src/main/scala/example/DottyPredef.scala b/semanticdb/input/src/main/scala/example/DottyPredef.scala new file mode 100644 index 000000000000..4f9215283fc2 --- /dev/null +++ b/semanticdb/input/src/main/scala/example/DottyPredef.scala @@ -0,0 +1,9 @@ +package example + +class PredefsDotty { + locally { + val x: Int => Int = _ => ??? + } + assert(true) + assert(false, "bonjour") +} \ No newline at end of file diff --git a/semanticdb/input/src/main/scala/example/Example.scala b/semanticdb/input/src/main/scala/example/Example.scala index f4eb0fbeb947..682c0ba7cea6 100644 --- a/semanticdb/input/src/main/scala/example/Example.scala +++ b/semanticdb/input/src/main/scala/example/Example.scala @@ -14,8 +14,9 @@ class Example { x + y ) + var b = 4 } -class ExampleInit() { +class ExampleInit { } \ No newline at end of file diff --git a/semanticdb/input/src/main/scala/example/Flags.scala b/semanticdb/input/src/main/scala/example/Flags.scala index 51c56d2fac47..dd96fa6621f8 100644 --- a/semanticdb/input/src/main/scala/example/Flags.scala +++ b/semanticdb/input/src/main/scala/example/Flags.scala @@ -1,11 +1,10 @@ -package example -package object p { +package object example { private lazy val x = 1 - protected implicit var y: Int = 2 + /*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) { + abstract class Cex[+T, -U, V](x: T, y: U, z: V) { def this() = this(???, ???, ???) def w: Int } @@ -20,5 +19,5 @@ package object p { class S[@specialized T] val List(xs1) = ??? ??? match { case List(xs2) => ??? } - ??? match { case _: List[t] => ??? } + ??? match { case _: List[t] => ??? }*/ } diff --git a/semanticdb/input/src/main/scala/example/IgnoredSymbol.scala b/semanticdb/input/src/main/scala/example/IgnoredSymbol.scala new file mode 100644 index 000000000000..13d58ac4132f --- /dev/null +++ b/semanticdb/input/src/main/scala/example/IgnoredSymbol.scala @@ -0,0 +1,8 @@ +package example + +class IgnoredTest { + val _ = 2 + def foo(x: Int): Unit = { + val _ = x + } +} \ No newline at end of file diff --git a/semanticdb/input/src/main/scala/example/Imports.scala b/semanticdb/input/src/main/scala/example/Imports.scala deleted file mode 100644 index 5f4ead1f23cc..000000000000 --- a/semanticdb/input/src/main/scala/example/Imports.scala +++ /dev/null @@ -1 +0,0 @@ -//import scala.util.control.NonFatal diff --git a/semanticdb/input/src/main/scala/example/MacroAnnotations.scala b/semanticdb/input/src/main/scala/example/MacroAnnotations.scala index 02276ed2ef6f..50f8576ecf6f 100644 --- a/semanticdb/input/src/main/scala/example/MacroAnnotations.scala +++ b/semanticdb/input/src/main/scala/example/MacroAnnotations.scala @@ -1,5 +1,20 @@ +/* +// This text is deactivated for now as macro annotations require to enable +// macro paradise + package example -//@MacroAnnotation + +import scala.annotation.StaticAnnotation +import scala.annotation.compileTimeOnly +import scala.language.experimental.macros + +@compileTimeOnly("enable macro paradise to expand macro annotations") +class MacroAnnotation extends StaticAnnotation { + def macroTransform(annottees: Any*): Any = ??? +} + +@MacroAnnotation class MacroAnnotations object MacroAnnotations +*/ \ No newline at end of file diff --git a/semanticdb/input/src/main/scala/example/MultiArguments.scala b/semanticdb/input/src/main/scala/example/MultiArguments.scala new file mode 100644 index 000000000000..88d61b83585a --- /dev/null +++ b/semanticdb/input/src/main/scala/example/MultiArguments.scala @@ -0,0 +1,5 @@ +package example + +class Multiple { + def m19(x: Int)(z: Int = 3) = ??? +} diff --git a/semanticdb/input/src/main/scala/example/New.scala b/semanticdb/input/src/main/scala/example/New.scala new file mode 100644 index 000000000000..77f32b0a7c33 --- /dev/null +++ b/semanticdb/input/src/main/scala/example/New.scala @@ -0,0 +1,17 @@ +package example + +class Bonjour() { +} + +class Bonjour2(val x:Int) { + def this(x:String) = this(2) +} + +class TestNew extends C { + val b = new B + val c = new Bonjour + val d = new Bonjour() + val e = new Bonjour2(2) + val f = new Bonjour2("a") + val _ = f.x +} \ No newline at end of file diff --git a/semanticdb/input/src/main/scala/example/SelfUse.scala b/semanticdb/input/src/main/scala/example/SelfUse.scala new file mode 100644 index 000000000000..db6ee485e586 --- /dev/null +++ b/semanticdb/input/src/main/scala/example/SelfUse.scala @@ -0,0 +1,5 @@ +package example + +class SelfUse extends B { a : B => +val c = a.b +} \ No newline at end of file diff --git a/semanticdb/input/src/main/scala/example/Selfs.scala b/semanticdb/input/src/main/scala/example/Selfs.scala index 06ed2c26709a..6226250d5452 100644 --- a/semanticdb/input/src/main/scala/example/Selfs.scala +++ b/semanticdb/input/src/main/scala/example/Selfs.scala @@ -1,6 +1,8 @@ package example -class B +class B { + val b = 4 +} class AC1 extends B { self => } diff --git a/semanticdb/input/src/main/scala/example/SemanticDoc.scala b/semanticdb/input/src/main/scala/example/SemanticDoc.scala new file mode 100644 index 000000000000..56e19d4118c7 --- /dev/null +++ b/semanticdb/input/src/main/scala/example/SemanticDoc.scala @@ -0,0 +1,12 @@ +package example + +abstract class Ctest(val xp: Int, val xe : AnyRef) { + val xm: Int = ??? + val xam: Int + private[this] val xlm: Int = ??? + def m = { + val xl: Int = ??? + type S = { val xs: Int } + type E = xe.type + } +} \ No newline at end of file diff --git a/semanticdb/input/src/main/scala/example/Super.scala b/semanticdb/input/src/main/scala/example/Super.scala new file mode 100644 index 000000000000..c052b9113769 --- /dev/null +++ b/semanticdb/input/src/main/scala/example/Super.scala @@ -0,0 +1,9 @@ +package example + +class SuperA { + def a(x:Int) = x +} + +class SuperB extends SuperA { + override def a(x:Int) = super.a(x) +} \ No newline at end of file diff --git a/semanticdb/input/src/main/scala/example/Synthetic.scala b/semanticdb/input/src/main/scala/example/Synthetic.scala index c888edc329e8..c0aaea853eff 100644 --- a/semanticdb/input/src/main/scala/example/Synthetic.scala +++ b/semanticdb/input/src/main/scala/example/Synthetic.scala @@ -43,5 +43,4 @@ class Synthetic { b <- scala.concurrent.Future.successful(2) if a < b } yield a - } diff --git a/semanticdb/input/src/main/scala/example/TypeBug.scala b/semanticdb/input/src/main/scala/example/TypeBug.scala new file mode 100644 index 000000000000..9b8226502269 --- /dev/null +++ b/semanticdb/input/src/main/scala/example/TypeBug.scala @@ -0,0 +1,15 @@ +package example + +import scala.language.existentials +import scala.language.higherKinds + +object TypBug { + class M { + def m: Int = ??? + } + class C extends M { + case class RepeatedType(s: String*) { + def m1(x: Int*): Int = s.length + } + } +} diff --git a/semanticdb/input/src/main/scala/example/Types.scala b/semanticdb/input/src/main/scala/example/Types.scala index a22b49115ead..904da9dbce76 100644 --- a/semanticdb/input/src/main/scala/example/Types.scala +++ b/semanticdb/input/src/main/scala/example/Types.scala @@ -3,9 +3,13 @@ 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 TypeM { + def m: Int = ??? +} + +class TypeC extends TypeM { + val superType = super[TypeM].m +} class TypB @@ -32,6 +36,10 @@ object TypTest { def n: Int = ??? } + class B { + def x:Int = 4 + } + class C extends M { val p = new TypP val x = p.x @@ -59,9 +67,6 @@ object TypTest { 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") @@ -70,7 +75,7 @@ object TypTest { typeLambda1[({ type L[T] = List[T] })#L] object ClassInfoType1 - class ClassInfoType2 extends B { def x = 42 } + class ClassInfoType2 extends B { override def x = 42 } trait ClassInfoType3[T] object MethodType { @@ -112,4 +117,4 @@ object TypTest { final val javaEnum = java.nio.file.LinkOption.NOFOLLOW_LINKS final val clazzOf = classOf[Option[Int]] } -} +} \ No newline at end of file diff --git a/semanticdb/input/src/main/scala/example/TypesAnnotations.scala b/semanticdb/input/src/main/scala/example/TypesAnnotations.scala new file mode 100644 index 000000000000..c4ef702686fc --- /dev/null +++ b/semanticdb/input/src/main/scala/example/TypesAnnotations.scala @@ -0,0 +1,13 @@ +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 + +object TypTestAnnots { + val annType1: T @ann(42) = ??? + val annType2: T @ann1 @ann2 = ??? +} diff --git a/semanticdb/src/dotty/semanticdb/Main.scala b/semanticdb/src/dotty/semanticdb/Main.scala index 9fe05a8c1572..f9e444573dc2 100644 --- a/semanticdb/src/dotty/semanticdb/Main.scala +++ b/semanticdb/src/dotty/semanticdb/Main.scala @@ -2,16 +2,101 @@ package dotty.semanticdb import scala.tasty.Reflection import scala.tasty.file._ +import scala.NotImplementedError + +import dotty.tools.dotc.Driver +import dotty.tools.dotc.reporting.Reporter + +import java.io.File +import java.nio.file._ object Main { + val userHome = System.getProperty("user.home") + val classpaths = + userHome + "/.ivy2/cache/org.scala-lang/scala-library/jars/scala-library-2.12.8.jar" :: + "out/bootstrap/dotty-library-bootstrapped/scala-0.12/dotty-library_0.12-0.12.0-bin-SNAPSHOT.jar" :: Nil + + val help = """Usage semanticdb [options] [file] + |Generate semanticdb's information related to the source file [file] + |Options are: + | -h,--help Show help + | -o , --out Place the output into (default: out.semanticdb) + | -t , --temp Use as the temp directory to store build artifacts + """.stripMargin + + type CliArgs = Map[String, String] + + def parseArguments(args: Array[String]): Option[CliArgs] = { + val optRegex = "$-.*".r + def nextArgument(optionMap: CliArgs, args: List[String]): Option[CliArgs] = args match { + case "--out" :: file :: tail => nextArgument(optionMap + ("out" -> file), tail) + case "-o" :: file :: tail => nextArgument(optionMap + ("out" -> file), tail) + case "--help" :: tail => nextArgument(optionMap + ("help" -> ""), tail) + case "-h" :: tail => nextArgument(optionMap + ("help" -> ""), tail) + case "--classpath" :: folder :: tail => nextArgument(optionMap + ("classpath" -> folder), tail) + case "-c" :: folder :: tail => nextArgument(optionMap + ("classpath" -> folder), tail) + case optRegex(_) :: _=> None + case file :: tail => nextArgument(optionMap + ("input" -> file), tail) + case Nil => Some(optionMap) + } + + nextArgument(Map(), args.toList) match { + case Some(args : CliArgs) => { + var cleanedArgs = args + cleanedArgs += "out" -> cleanedArgs.getOrElse("out", "out.semanticdb") + if (cleanedArgs.contains("help") || !cleanedArgs.contains("input")) { + None + } else { + cleanedArgs += "classpath" -> cleanedArgs.getOrElse("classpath", Files.createTempDirectory("semanticdb").toString) + val tempFolder = new File(cleanedArgs("classpath")); + if (!tempFolder.exists()){ + tempFolder.mkdir(); + } + Some(cleanedArgs) + } + } + case None => None + } + } + + def compile(cliArgs : CliArgs) : Reporter + = { + val driver = new Driver + val compilerParams : List[String] = + "-classpath" :: classpaths.mkString(":") :: + "-Yno-inline" :: + "-d" :: cliArgs("classpath") :: + cliArgs("input") :: + Nil + + driver.process(compilerParams.toArray) + } + + def main(args: Array[String]): Unit = { val extraClasspath = "." // TODO allow to set it from the args with -classpath XYZ val classes = args.toList - if (args.isEmpty) { - println("Dotty Semantic DB: No classes where passed as argument") - } else { - println("Running Dotty Semantic DB on: " + args.mkString(" ")) - ConsumeTasty(extraClasspath, classes, new SemanticdbConsumer) + + + parseArguments(args) match { + case None => println(help) + case Some(cliArgs) => { + val reporter = compile(cliArgs) + + if (reporter.hasErrors) { + println("Compile error:") + println(reporter) + } else { + val scalaFile = Paths.get(cliArgs("input")).toAbsolutePath + val classNames = Utils.getClassNames(Paths.get(cliArgs("classpath")), scalaFile) + val sdbconsumer = new SemanticdbConsumer(scalaFile) + val _ = ConsumeTasty(cliArgs("classpath"), classNames, sdbconsumer) + val textDocument = sdbconsumer.toSemanticdb() + val os = Files.newOutputStream(Paths.get(cliArgs("out"))) + try textDocument.writeTo(os) + finally os.close() + } + } } } } diff --git a/semanticdb/src/dotty/semanticdb/Scala.scala b/semanticdb/src/dotty/semanticdb/Scala.scala index 94f2d76b7bc0..2d752e8b3c0d 100644 --- a/semanticdb/src/dotty/semanticdb/Scala.scala +++ b/semanticdb/src/dotty/semanticdb/Scala.scala @@ -173,7 +173,9 @@ object Scala { val Constructor: TermName = TermName("") private[semanticdb] def encode(value: String): String = { - if (value == "") { + if (value == "scalaShadowing") { + "scala" + } else if (value == "") { "``" } else { val (start, parts) = (value.head, value.tail) diff --git a/semanticdb/src/dotty/semanticdb/SemanticdbConsumer.scala b/semanticdb/src/dotty/semanticdb/SemanticdbConsumer.scala index ae02f485bf1a..bfdac6b40c8a 100644 --- a/semanticdb/src/dotty/semanticdb/SemanticdbConsumer.scala +++ b/semanticdb/src/dotty/semanticdb/SemanticdbConsumer.scala @@ -4,39 +4,54 @@ import scala.tasty.Reflection import scala.tasty.file.TastyConsumer import dotty.tools.dotc.tastyreflect +import dotty.tools.dotc.core.StdNames._ 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 { + +class SemanticdbConsumer(sourceFilePath: java.nio.file.Path) 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) + def toSemanticdb(): s.TextDocument = { + s.TextDocument(text = sourceCode.content(), occurrences = occurrences) } - val package_definitions: Set[String] = Set() + + // Caching for package definitions (as they are shared accross different class files) + val packageDefinitions: Set[(String, Int)] = Set() + // Caching for symbol paths to avoid regenerating some of them + // (as computing a symbol path from a symbol is deterministic) + val symbolsCache: HashMap[(Any, s.Range), String] = HashMap() + // Offset for local symbol + var localOffset: Int = 0 + + val sourceCode = new SourceFile(sourceFilePath) final def apply(reflect: Reflection)(root: reflect.Tree): Unit = { import reflect._ - val symbolsCache: HashMap[Symbol, String] = HashMap() + // To avoid adding symbol paths duplicates inside a same class + val symbolPathsMap: Set[(String, s.Range)] = Set() object ChildTraverser extends TreeTraverser { var children: List[Tree] = Nil + var childrenType: List[TypeOrBoundsTree] = 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 = () + implicit ctx: Context): Unit = + childrenType = tree :: childrenType override def traverseCaseDef(tree: CaseDef)(implicit ctx: Context): Unit = () - override def traverseTypeCaseDef(tree: TypeCaseDef)(implicit ctx: Context): Unit = + override def traverseTypeCaseDef(tree: TypeCaseDef)( + implicit ctx: Context): Unit = () def getChildren(tree: Tree)(implicit ctx: Context): List[Tree] = { @@ -44,6 +59,19 @@ class SemanticdbConsumer extends TastyConsumer { traverseTreeChildren(tree)(ctx) return children } + def getChildrenType(tree: TypeOrBoundsTree)(implicit ctx: Context): List[TypeOrBoundsTree] = { + childrenType = Nil + traverseTypeTreeChildren(tree)(ctx) + return childrenType + } + } + + /* The (==) operator does not work correctly on two positions, + we redefine our one */ + def arePositionEqual(p1 : Position, p2 : Position) : Boolean = { + p1.start == p2.start && + p1.end == p2.end && + p1.sourceFile == p2.sourceFile } object Traverser extends TreeTraverser { @@ -51,231 +79,970 @@ class SemanticdbConsumer extends TastyConsumer { 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)) + return !((tree.pos.exists && tree.pos.start == tree.pos.end && children == Nil) || + children.exists(arePositionEqual(tree.pos, _))) } } implicit class TypeTreeExtender(tree: TypeTree) { + def isUserCreated: Boolean = { + val children: List[Position] = + ChildTraverser.getChildrenType(tree)(reflect.rootContext).collect(_ match { + case IsTypeTree(tt) => tt.pos}) + return !((tree.pos.exists && tree.pos.start == tree.pos.end && children == Nil) || + children.exists(arePositionEqual(tree.pos, _))) + } + } + + implicit class TermOrTypeTreeExtender(tree: TermOrTypeTree) { + def pos: Position = tree match { + case IsTerm(t) => t.pos + case IsTypeTree(t) => t.pos + } + + def symbol: Symbol = tree match { + case IsTerm(t) => t.symbol + case IsTypeTree(t) => t.symbol + } + } + + implicit class TypeOrBoundsTreeExtender(tree: TypeOrBoundsTree) { + def typetree: TypeTree = tree match { + case IsTypeTree(t) => t + } + } + + implicit class PatternExtender(tree: Pattern) { 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.is(Flags.Param) - case _ => false + def exists = !(symbol.name == "" || symbol == NoSymbol) + /* Return true if symbol represents the definition of a var setter, false otherwise. + We return true if the extract of source code corresponding to the position of the symbol is the same as the symbol name. + Ex: + var m = ??? + -> there is a defdef for `m_=` with position "m =". As "m =" != "m_=", we return false + */ + def isMutableSetterExplicit(role : s.SymbolOccurrence.Role) = { + if (role == s.SymbolOccurrence.Role.DEFINITION && + symbol.pos.exists && + symbol.flags.is(Flags.Mutable) && symbol.isMethod && + symbol.trueName.endsWith("_=")) + (sourceCode.peek(symbol.pos.start, symbol.pos.end) == symbol.trueName) + else + true + } + + // The name of a symbol can contain special chars. This will replace them with the correct char. + def trueName: String = { + val prohibitedChars = '.' :: ';' :: '[' :: '/' :: '<' :: '>' :: Nil + prohibitedChars.foldLeft(symbol.name)((old, chr) => + old.replaceAll("\\$u%04X".format(chr.toInt), chr.toString) + ) + } + + + def isClass: Boolean = symbol match { + case IsClassSymbol(_) => true + case _ => false } + def isTypeParameter: Boolean = symbol.isParameter && symbol.isType + def isType: Boolean = symbol match { case IsTypeSymbol(_) => true case _ => false } + def isTerm: Boolean = !symbol.isType + def isMethod: Boolean = symbol match { case IsDefSymbol(_) => true case _ => false } + def isVal: Boolean = symbol match { + case IsValSymbol(_) => true + case _ => false + } + def isPackage: Boolean = symbol match { case IsPackageSymbol(_) => true case _ => false } + def isDefaultGetter: Boolean = + symbol.name.contains(tpnme.DEFAULT_GETTER.toString) + + def isReservedName : Boolean = { + val keywords = + List("ev$", "evidence$", "$_lazy_implicit_$", "$lzy", "$lzyINIT", + "$OFFSET", "bitmap$", "_$", "$tailLocal", "tmp", "$doc", + "$superArg$", "$scrutinee", "$elem") + return keywords.exists(symbol.name.contains(_)) + } + + def isParameter: Boolean = symbol.flags.is(Flags.Param) + def isObject: Boolean = symbol.flags.is(Flags.Object) def isTrait: Boolean = symbol.flags.is(Flags.Trait) - def isValueParameter: Boolean = symbol.flags.is(Flags.Param) + def isConstructor(implicit ctx: Context): Boolean = + symbol.name == "" + + def isVarAccessor(implicit ctx: Context): Boolean = { + symbol.isVal && symbol.flags.is(Flags.Mutable) + } + + def isValMethod(implicit ctx: Context): Boolean = { + symbol.isMethod && { + (symbol.flags.is(Flags.FieldAccessor) && symbol.flags.is(Flags.StableRealizable) ) || + (symbol.isUsefulField && !symbol.flags.is(Flags.Mutable) ) + } + } + + def isAnonymousClassConstructor(implicit ctx: Context): Boolean = { + symbol.isConstructor && symbol.owner.isAnonymousClass + } + + def isAnonymousSelfParameter(implicit ctx: Context): Boolean = { + symbol.isSelfParameter && { + symbol.name == tpnme.this_.toString || // hardlinked in ClassSignature.self + symbol.name.startsWith("x$") // wildcards can't be referenced: class A { _: B => } + } + } + + def isWildCard(implicit ctx: Context): Boolean = { + symbol.name.startsWith(tpnme.WILDCARD.toString) && + symbol.name != tpnme.THIS.toString + } + + def isAnonymousInit(implicit ctx: Context): Boolean = { + return symbol.exists && symbol.owner.exists && + (symbol.owner.isAnonymousFunction || symbol.owner.isAnonymousClass) && + symbol.name == "" + } + + /* The following methods are directly extracted from the scala + implementation of SemanticDB (scalameta/semanticdb/scalac/library/src/main/scala/scala/meta/internal/semanticdb/scalac/SymbolOps.scala) + */ + def isValueParameter: Boolean = symbol.isParameter && !symbol.isType && !symbol.flags.is(Flags.ParamAccessor) + + def isJavaClass: Boolean = (symbol.isClass || symbol.isObject) && symbol.flags.is(Flags.JavaDefined) + + def isSelfParameter(implicit ctx: Context): Boolean = + symbol.exists && symbol.owner == symbol + + def isSemanticdbLocal(implicit ctx: Context): Boolean = { + def definitelyGlobal = symbol.isPackage + def definitelyLocal = + !symbol.exists || + (symbol.owner.isTerm && !symbol.isParameter) || + ((symbol.owner.isAliasType || symbol.owner.isAbstractType) && !symbol.isParameter) || + symbol.isSelfParameter || + symbol.isLocalDummy || + symbol.isRefinementClass || + symbol.isAnonymousClass || + symbol.isAnonymousFunction /*|| + symbol.isExistential*/ + def ownerLocal = symbol.owner.isSemanticdbLocal + !definitelyGlobal && (definitelyLocal || ownerLocal) + } + + def isSyntheticConstructor(implicit ctx: Context): Boolean = { + val isObjectConstructor = symbol.isConstructor && symbol.owner.exists && symbol.owner.flags.is(Flags.Object) + val isModuleConstructor = symbol.isConstructor && symbol.owner.isClass + val isTraitConstructor = symbol.isConstructor && symbol.owner.isTrait + val isInterfaceConstructor = symbol.isConstructor && symbol.owner.flags.is(Flags.JavaDefined) && symbol.owner.isTrait + val isEnumConstructor = symbol.isConstructor && symbol.owner.flags.is(Flags.JavaDefined) && symbol.owner.flags.is(Flags.Enum) + /*val isStaticConstructor = symbol.name == g.TermName("")*/ + //val isClassfileAnnotationConstructor = symbol.owner.isClassfileAnnotation + /*isModuleConstructor || */ + isTraitConstructor || isInterfaceConstructor || isObjectConstructor || + isEnumConstructor /*|| isStaticConstructor || isClassfileAnnotationConstructor*/ + } + def isLocalChild(implicit ctx: Context): Boolean = + symbol.name == tpnme.LOCAL_CHILD.toString + + def isSyntheticValueClassCompanion(implicit ctx: Context): Boolean = { + if (symbol.isClass) { + if (symbol.flags.is(Flags.Object)) { + symbol.asClass.moduleClass.fold(false)(c => + c.isSyntheticValueClassCompanion) + } else { + symbol.flags.is(Flags.ModuleClass) && + symbol.flags.is(Flags.Synthetic) && + symbol.asClass.methods.isEmpty + } + } else { + false + } + } + + /* the `isFieldForPrivateThis` is commented out otherwise class members of the form + "private[this] val foo" are not converted to symbol occurences. + In the original semanticdb this line is commented out. + */ + def isScalacField(implicit ctx: Context): Boolean = { + //val isFieldForPrivateThis = symbol.flags.is(Flags.PrivateLocal) && symbol.isTerm && !symbol.isMethod && !symbol.isObject + val isFieldForOther = false //symbol.name.endsWith(g.nme.LOCAL_SUFFIX_STRING) + val isJavaDefined = symbol.flags.is(Flags.JavaDefined) + (/*isFieldForPrivateThis ||*/ isFieldForOther) && !isJavaDefined + } + def isUselessField(implicit ctx: Context): Boolean = { + symbol.isScalacField && symbol.owner.exists + } + def isUsefulField(implicit ctx: Context): Boolean = { + symbol.isScalacField && !symbol.isUselessField + } + def isSyntheticCaseAccessor(implicit ctx: Context): Boolean = { + symbol.flags.is(Flags.CaseAcessor) && symbol.trueName.contains("$") + } + def isSyntheticJavaModule(implicit ctx: Context): Boolean = { + val resolved = symbol match { + case IsClassSymbol(c) => resolveClass(c) + case _ => symbol + } + !resolved.flags.is(Flags.Package) && resolved.flags.is(Flags.JavaDefined) && resolved.flags.is(Flags.Object) + } + def isSyntheticAbstractType(implicit ctx: Context): Boolean = { + symbol.flags.is(Flags.Synthetic) && symbol.isAbstractType // these are hardlinked to TypeOps + } + def isEtaExpandedParameter(implicit ctx: Context): Boolean = { + // Term.Placeholder occurrences are not persisted so we don't persist their symbol information. + // We might want to revisit this decision https://github.com/scalameta/scalameta/issues/1657 + symbol.isParameter && + symbol.name.startsWith("x$") && + symbol.owner.isAnonymousFunction + } + def isStaticMember(implicit ctx: Context): Boolean = + symbol.exists && + (symbol.flags.is(Flags.Static) || symbol.owner.flags.is(Flags.ImplClass) || + /*symbol.annots.find(_ == ctx.definitions.ScalaStaticAnnot)*/ false) - // TODO : implement it - def isJavaClass: Boolean = false + def isStaticConstructor(implicit ctx: Context): Boolean = { + (symbol.isStaticMember && symbol.isClassConstructor) || (symbol.name == tpnme.STATIC_CONSTRUCTOR.toString) + } + + /* End of methods imported from the scala version of SemanticDB */ + + def isInitChild(implicit ctx: Context): Boolean = { + if (symbol.exists && symbol.owner.exists) { + return symbol.owner.name == "" || symbol.owner.isInitChild + } else { + return false + } + } + + def isUseless(implicit ctx: Context): Boolean = { + !symbol.exists || + symbol.isReservedName || + symbol.isAnonymousInit || + symbol.isDefaultGetter || + symbol.isWildCard || + symbol.isAnonymousClass || + symbol.isAnonymousFunction || + symbol.isSyntheticConstructor || + symbol.isStaticConstructor || + symbol.isLocalChild || + symbol.isSyntheticValueClassCompanion || + symbol.isUselessField || + symbol.isSyntheticCaseAccessor || + symbol.isRefinementClass || + symbol.isSyntheticJavaModule + } + def isUseful(implicit ctx: Context): Boolean = !symbol.isUseless + def isUselessOccurrence(implicit ctx: Context): Boolean = { + symbol.isUseless && + !symbol.isSyntheticJavaModule // references to static Java inner classes should have occurrences + } } def resolveClass(symbol: ClassSymbol): Symbol = (symbol.companionClass, symbol.companionModule) match { + case (Some(c), _) => c case (_, Some(module)) if symbol.flags.is(Flags.Object) => module - case (Some(c), _) => c - case _ => symbol + 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 + def disimbiguate(symbolPath: String, symbol: Symbol): String = { + try { + val symbolcl = resolveClass(symbol.owner.asClass) + symbolcl match { + case IsClassSymbol(classsymbol) => { + val methods = classsymbol.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 + ")" + if (real_pos == 0) { + "()" + } else { + "(+" + real_pos + ")" + } + } + case _ => { + "()" + } + } + } catch { + case _ => "()" } } - def iterateParent(symbol: Symbol): String = { - if (symbolsCache.contains(symbol)) { - return symbolsCache(symbol) + def iterateParent(symbol: Symbol, isMutableAssignement:Boolean=false): String = { + if (!symbol.exists || symbol.name == "") then { + "" } 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) - } + val rsymbol = symbol match { + case IsClassSymbol(c) => resolveClass(c) + case _ => symbol + } + val previous_symbol = + /* When we consider snipper of the form: `abstract class DepAdvD[CC[X[C] <: B], X[Z], C] extends DepTemp`, + The symbol for C will be something like example/DepAdvD#``().[CC].[X].[C]. + This is illogic: a init method can't have any child. Thus, when the current symbol is + a typeparameter, and the owner is an init, we can just "jump" over the init. */ + if (rsymbol.owner.name == "" && rsymbol.isType) + iterateParent(rsymbol.owner.owner) + else + iterateParent(rsymbol.owner) + - Symbols.Global(previous_symbol, next_atom) + val isdef = rsymbol match {case IsDefSymbol(_) => true case _ => false} + val symbolName = if (isMutableAssignement) rsymbol.trueName + "_=" else rsymbol.trueName + val next_atom = + if (rsymbol.isPackage) { + d.Package(symbolName) + } else if (rsymbol.isObject && !rsymbol.isJavaClass) { + d.Term(symbolName) + } else if (rsymbol.isValMethod && !rsymbol.isVarAccessor) { + d.Term(symbolName) + } else if (rsymbol.isMethod || rsymbol.isUsefulField || rsymbol.isVarAccessor) { + d.Method(symbolName, + disimbiguate(previous_symbol + symbolName, rsymbol)) + } else if (rsymbol.isTypeParameter) { + d.TypeParameter(symbolName) + } else if (rsymbol.isValueParameter) { + d.Parameter(symbolName) + } else if (rsymbol.isType || rsymbol.isJavaClass) { + d.Type(symbolName) + } else { + d.Term(symbolName) } - symbolsCache += (symbol -> out_symbol_path) - out_symbol_path + + Symbols.Global(previous_symbol, next_atom) + } + } + + def symbolToSymbolString(symbol: Symbol, isMutableAssignement:Boolean = false): (String, Boolean) = { + if (symbol.isSemanticdbLocal) { + var localsymbol = Symbols.Local(localOffset.toString) + localOffset += 1 + (localsymbol, false) + } else { + (iterateParent(symbol, isMutableAssignement), true) } } def addOccurence(symbol: Symbol, - type_symbol: s.SymbolOccurrence.Role, - range: s.Range): Unit = { - val symbol_path = iterateParent(symbol) - if (symbol_path == "") return + typeSymbol: s.SymbolOccurrence.Role, + range: s.Range, + isMutableAssignement:Boolean = false): Unit = { + if (!symbol.exists) return + val symbolName = if (isMutableAssignement) symbol.trueName + "_=" else symbol.trueName + val (symbolPath, isGlobal) = + if (symbol.pos.exists) { + val keyRange = createRange(symbol.pos) + if (symbolsCache.contains((symbolName, keyRange))) + (symbolsCache((symbolName, keyRange)), symbol.isSemanticdbLocal) + else { + val (sp, ig) = symbolToSymbolString(symbol, isMutableAssignement) + symbolsCache += ((symbolName, keyRange) -> sp) + (sp, ig) + } + } else { + symbolToSymbolString(symbol) + } + + if (symbolPath == "") return + if (symbol.flags.is(Flags.Synthetic) && typeSymbol == s.SymbolOccurrence.Role.DEFINITION) return + + val key = (symbolPath, range) // this is to avoid duplicates symbols + // For example, when we define a class as: `class foo(x: Int)`, + // dotty will generate a ValDef for the x, but the x will also + // be present in the constructor, thus making a double definition + if (!symbolPathsMap.contains(key)) { + symbolPathsMap += key + occurrences = + occurrences :+ + s.SymbolOccurrence( + Some(range), + symbolPath, + typeSymbol + ) + } + } + + def addOccurencePredef(parent: String, name: String, range: s.Range): Unit = { occurrences = occurrences :+ s.SymbolOccurrence( Some(range), - symbol_path, - type_symbol + parent + name + "().", + s.SymbolOccurrence.Role.DEFINITION ) + } + def addSelfDefinition(name: String, range: s.Range): Unit = { + var localsymbol = Symbols.Local(localOffset.toString) + localOffset += 1 + symbolsCache += ((name, range) -> localsymbol) + occurrences = + occurrences :+ + s.SymbolOccurrence( + Some(range), + localsymbol, + s.SymbolOccurrence.Role.DEFINITION + ) } def addOccurenceTree(tree: Tree, - type_symbol: s.SymbolOccurrence.Role, + typeSymbol: s.SymbolOccurrence.Role, range: s.Range, - force_add: Boolean = false): Unit = { - if (tree.isUserCreated || force_add) { - addOccurence(tree.symbol, type_symbol, range) + forceAdd: Boolean = false, + isMutableAssignement: Boolean = false): Unit = { + if (!tree.symbol.isUselessOccurrence && + tree.symbol.isMutableSetterExplicit(typeSymbol) && + (tree.isUserCreated || forceAdd)) { + addOccurence(tree.symbol, typeSymbol, range, isMutableAssignement) } } + def addOccurenceTypeTree(typetree: TypeTree, - type_symbol: s.SymbolOccurrence.Role, + typeSymbol: s.SymbolOccurrence.Role, range: s.Range): Unit = { - if (typetree.isUserCreated) { - addOccurence(typetree.symbol, type_symbol, range) + if (!typetree.symbol.isUselessOccurrence && typetree.isUserCreated) { + addOccurence(typetree.symbol, typeSymbol, range) } } - def range(tree: Tree, pos: Position, name: String): s.Range = { - val offset = tree match { - case IsPackageClause(tree) => "package ".length - case _ => 0 + def addOccurencePatternTree(tree: Pattern, + typeSymbol: s.SymbolOccurrence.Role, + range: s.Range): Unit = { + if (!tree.symbol.isUselessOccurrence && tree.isUserCreated) { + addOccurence(tree.symbol, typeSymbol, range) } + } + + def addOccurenceId(parentPath: String, id: Id): Unit = { + val symbolPath = Symbols.Global(parentPath, d.Term(id.name)) + occurrences = + occurrences :+ + s.SymbolOccurrence( + Some(createRange(id.pos)), + symbolPath, + s.SymbolOccurrence.Role.REFERENCE + ) + } + + def createRange(pos: Position): s.Range = + createRange(pos.startLine, pos.startColumn, pos.endLine, pos.endColumn) + + def createRange(startLine : Int, startColumn : Int, length : Int) : s.Range = { + createRange(startLine, startColumn, startLine, startColumn + length) + } - val range_end_column = - if (name == "") { - pos.endColumn + def createRange(startLine: Int, startColumn: Int, endLine: Int, endColumn: Int): s.Range = { + /* This aux function is to make sure every generated range are coherent, + meaning they all have a valid startLine and startColumn (meaning startColumn is + a number of byte from the start of the line, not the start of the file)*/ + def aux(l : Int, c : Int) : (Int, Int) = { + if (l == 0) { + val line = sourceCode.offsetToLine(l) + (line, c - sourceCode.lineToOffset(line)) } else { - pos.startColumn + name.length + (l, c) } + } - s.Range(pos.startLine, - pos.startColumn + offset, - pos.startLine, - range_end_column + offset) + val (sl, sc) = aux(startLine, startColumn) + val (el, ec) = aux(endLine, endColumn) + s.Range(sl, sc, el, ec) } - 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) + /* Create a "point range" (a range refering to a point position) */ + def createRange(line: Int, column: Int) : s.Range = { + createRange(line, column, line, column) } - def typetreeSymbol(tree: Tree, typetree: TypeTree): Unit = - typetree match { - case TypeTree.Inferred => () - case _ => - addOccurenceTypeTree( - typetree, + def rangeSelect(name: String, range: Position): s.Range = { + if (name == "") { + return createRange(range) + } else + /* The position of a select is the position of the whole select expression, + from the start to the end. + To get the position of only the selected operand, we distinguish two cases: + - either we are selecting an operator ending with a ':' (for those the execution + order is reversed), so the selected expression is at the start. + Ex: A #:: B -> the position of the select is the range "#:: B", so we pick the range "#::" + - either the select is in normal order, in this case we select the end of it. + Ex: A + B -> the position of the select is the range "A +", so we pick the range "+" + */ + if (name.endsWith(":")) { + return createRange(range.startLine, range.startColumn, name.length) + } else { + return createRange(range.endLine, range.endColumn - name.length, name.length) + } + } + + def getImportPath(pathTerm: Term): String = { + val range = pathTerm match { + case Term.Select(qualifier, selected) => { + getImportPath(qualifier) + rangeSelect(selected, pathTerm.pos) + } + case Term.Ident(x) => { + createRange(pathTerm.pos.startLine, pathTerm.pos.startColumn, pathTerm.symbol.trueName.length) + } + } + addOccurenceTree(pathTerm, + s.SymbolOccurrence.Role.REFERENCE, + range) + iterateParent(pathTerm.symbol) + } + + + /* A known bug with import path is that we are not able to determine the nature of the + imported symbol (or to append several of them if we are importing both a class + and its companionmodule for exemple) */ + def getImportSelectors(parentPath: String, + selectors: List[ImportSelector]): Unit = { + selectors.foreach(selector => + selector match { + case SimpleSelector(id) if id.name != "_" => { + addOccurenceId(parentPath, id) + } + case RenameSelector(id, _) if id.name != "_" => { + addOccurenceId(parentPath, id) + } + case OmitSelector(id) if id.name != "_" => { + addOccurenceId(parentPath, id) + } + case _ => + }) + } + + override def traverseTypeTree(tree: TypeOrBoundsTree)( + implicit ctx: Context): Unit = { + tree match { + case TypeTree.Ident(_) => { + val typetree = tree.typetree + addOccurenceTypeTree(typetree, + s.SymbolOccurrence.Role.REFERENCE, + createRange(typetree.pos)) + } + case TypeTree.Select(qualifier, _) => { + val typetree = tree.typetree + val range = rangeSelect(typetree.symbol.trueName, typetree.pos) + addOccurenceTypeTree(typetree, + s.SymbolOccurrence.Role.REFERENCE, + range) + super.traverseTypeTree(typetree) + } + + case TypeTree.Projection(qualifier, x) => { + val typetree = tree.typetree + val range = rangeSelect(typetree.symbol.trueName, typetree.pos) + addOccurenceTypeTree(typetree, + s.SymbolOccurrence.Role.REFERENCE, + range) + super.traverseTypeTree(typetree) + } + + case TypeTree.Inferred() => { + /* In theory no inferred types should be put in the semanticdb file. + However, take the case where a typed is refered from an imported class: + class PrefC { + object N { + type U + } + } + + object PrefTest { + val c: PrefC = ??? + import c.N._ + def k3: U = ??? + } + + The type corresponding to U in the definition of k3 is marked as + inferred even though it is present in the source code. We use a + workaround for this specific case, by checking if the name of the + inferred type corresponds to the one put in the source code at this + position + */ + + val typetree = tree.typetree + val start = typetree.pos.start + val end = typetree.pos.end + if (sourceCode.peek(start, end) == typetree.symbol.name) { + addOccurenceTypeTree(typetree, + s.SymbolOccurrence.Role.REFERENCE, + createRange(typetree.pos)) + } + } + + case _ => { + super.traverseTypeTree(tree) + } + } + } + + override def traversePattern(tree: Pattern)(implicit ctx: Context): Unit = { + tree match { + case Pattern.Bind(name, _) => { + addOccurencePatternTree( + tree, s.SymbolOccurrence.Role.REFERENCE, - range(tree, typetree.pos, typetree.symbol.name)) + createRange(tree.symbol.pos.startLine, tree.symbol.pos.startColumn, name.length) + ) + super.traversePattern(tree) + } + case _ => + super.traversePattern(tree) + } + } + + + /* Finding the range of init symbols is not intuitive. We can determine it on a classdef. + [fittedInitClassRange] is used to transmit this information to the corresponding symbol */ + var fittedInitClassRange: Option[s.Range] = None + + /* At each point of the traversal [classStacks] is the list of classes currently being defined + Ex: + class Foo { + class Bar { + ??? // classStacks = Bar :: Foo :: Nil + } + ??? // classStacks = Foo :: Nil } + */ + var classStacks : List[Symbol] = Nil + + /* Is the term we are currently seeing the rhs of an assignement? */ + var isAssignedTerm = false + + /* Create a mapping from parameter name to parameter position */ + def generateParamsPosMapping(cdef: DefDef)(implicit ctx: Context): Map[String, s.Range] = { + val DefDef(_, _, params, _, _) = cdef + val start = Map[String, s.Range]() + params.foldLeft(start)((old, statements) => { + statements.foldLeft(old)((old, cval) => { + old + (cval.name -> createRange(cval.symbol.pos.startLine, cval.symbol.pos.startColumn, cval.symbol.trueName.length)) + }) + } + ) + } 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 _ => () + case Import(path, selectors) => + val key = (tree.symbol.trueName, tree.pos.start) + if (!packageDefinitions(key)) { + packageDefinitions += key + getImportSelectors(getImportPath(path), selectors) } + case Term.New(ty) => { + super.traverseTree(tree) + } + case Term.Apply(_, _) => { + super.traverseTree(tree) + } + case ClassDef(classname, constr, parents, derived, selfopt, statements) => { + val offsetSymbolClass = + if(tree.symbol.flags.is(Flags.Object)) -1 + else 0 - 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) + // we first add the class to the symbol list + addOccurenceTree(tree, + s.SymbolOccurrence.Role.DEFINITION, + createRange(tree.symbol.pos.startLine, + tree.symbol.pos.startColumn + offsetSymbolClass, + tree.symbol.trueName.length)) + + /* Before adding the constructor symbol, we must find its position. Two options here: + - we've got no type parameters: `class Foo {...}` -> the symbol is put after `Foo` + - we've got some typeparameters: `class Foo[X] {...}` -> the symbol is put after [X] + In order to find the correct position in the last case, we put ourself on the rightmost bound + of all type parameters, that means before the last ']'. Then, we move one character right to + pass the ']' while making sure to skip whitespaces and comments */ + + /* The position is put in [fittedInitClassRange] to be transmitted to the defdef of */ + val DefDef(_, typesParameters, _, _, _) = constr + if (typesParameters.isEmpty) { + fittedInitClassRange = Some( + createRange(tree.symbol.pos.startLine, + tree.symbol.pos.startColumn + classname.length)) + } else { + val rightmost = typesParameters.reverse.head.pos.end + val end_ = sourceCode.nextCharacterSkipComments(rightmost) + 1 + fittedInitClassRange = Some(createRange(0, end_)) + } + + traverseTree(constr) + + fittedInitClassRange = None + + // we add the parents to the symbol list + parents.foreach(_ match { + case IsTypeTree(t) => traverseTypeTree(t) + case IsTerm(t) => traverseTree(t) + }) + + selfopt match { + case Some(vdef @ ValDef(name, type_, _)) => { + // If name is "_" then it means it is in fact "this". We don't + // want to had a symbol for it in semanticdb + if (name != "_") { + // The tree does not include a position to the overloaded version of + // this. We find it heuristically by "parsing" the source code. + // The process is done in three steps: + // 1) Find a position before the '{' of the self but after any + // non related '{'. Here, it will be the largest end pos of a parent + // 2) Find the first '{' + // 3) Iterate until the character we are seeing is a letter + val startPosSearch: Int = parents.foldLeft(tree.pos.end)( + (old: Int, ct: TermOrTypeTree) => + ct match { + case IsTerm(t) if t.pos.end < old => t.pos.end + case _ => old + }) + + var posColumn = if (startPosSearch == tree.pos.end) tree.pos.start else startPosSearch + posColumn = sourceCode.firstOccurrenceLetter('{', posColumn) + posColumn = sourceCode.nextCharacterSkipComments(posColumn+1) + + addSelfDefinition(name, createRange(0, posColumn, name.length)) + } + traverseTypeTree(type_) + } + case _ => + } + + derived.foreach(traverseTypeTree) + + /* The last part is to go through every statements. + As usual, we must take care of how we do it as some statements are + accessors for parameters and we don't want to add duplicate information. + If a statement is a parameter accessor we add the corresponding occurence as it + wasn't done when we saw the symbol. + If it's only a parameter (meaning a type parameter) we already added it + before, so we do nothing. + Otherwise we proceed a usual + */ + classStacks = tree.symbol :: classStacks + + val paramsPosMapping = generateParamsPosMapping(constr) + + statements.foreach(statement => { + if (statement.symbol.flags.is(Flags.ParamAccessor)) { + if (paramsPosMapping.contains(statement.symbol.name)) { + addOccurenceTree(statement, s.SymbolOccurrence.Role.DEFINITION, paramsPosMapping(statement.symbol.name)) + } + } else if (!statement.symbol.flags.is(Flags.Param)) { + traverseTree(statement) + } + }) + + classStacks = classStacks.tail + } + + // If we have a symbol with a [fittedInitClassRange] we are sure it is a primary constructor + // We only record symbols correponding to types as symbols for value parameters will be added + // by traversing the class statements. + // Statement should be in this case + case DefDef("", typeparams, valparams, type_, statements) if fittedInitClassRange != None => { + addOccurenceTree(tree, + s.SymbolOccurrence.Role.DEFINITION, + fittedInitClassRange.get, + true) + valparams.foreach(params => + params.foreach(param => { + val ValDef(_, tpt, _) = param + traverseTypeTree(tpt) + }) + ) + traverseTypeTree(type_) + typeparams.foreach(traverseTree) + } + + // An object should have no init symbols + case DefDef("", _, _, _, _) if tree.symbol.owner.flags.is(Flags.Object) => { + } + + case Term.Assign(lhs, rhs) => { + // We make sure to set [isAssignedTerm] to true on the lhs + isAssignedTerm = true + traverseTree(lhs) + isAssignedTerm = false + traverseTree(rhs) + } + case IsDefinition(cdef) => { + // For a definition we must deal the special case of protected and private + // definitions + if (cdef.symbol.flags.is(Flags.Protected)) { + cdef.symbol.protectedWithin match { + case Some(within) => { + val startColumn = cdef.pos.startColumn + "protected[".length + addOccurence( + within.typeSymbol, + s.SymbolOccurrence.Role.REFERENCE, + createRange(cdef.pos.startLine, startColumn, within.typeSymbol.trueName.length) + ) + } + case _ => + } } else { + cdef.symbol.privateWithin match { + case Some(within) => { + val startColumn = cdef.pos.startColumn + "private[".length + addOccurence( + within.typeSymbol, + s.SymbolOccurrence.Role.REFERENCE, + createRange(cdef.pos.startLine, startColumn, within.typeSymbol.trueName.length) + ) + } + case _ => + } + } + + if (tree.symbol.exists) { + val pos = tree.symbol.pos + var rangeSymbol = createRange(pos.startLine, pos.startColumn, tree.symbol.trueName.length) + + // In dotty definition of auxilliary constructors (ex def this(xxxx)) are represented + // by a DefDef("", ..). This conditions finds such patterns and set a correct rangeSymbol for them. + if (tree.symbol.trueName == "" && sourceCode.peek(pos.start, pos.start + 4) == "this") { + rangeSymbol = createRange(pos.startLine, pos.startColumn, 4) + } addOccurenceTree(tree, s.SymbolOccurrence.Role.DEFINITION, - range_symbol) + rangeSymbol) + + } + super.traverseTree(cdef) + } + + case Term.This(Some(id)) => { + /* We've got two options here: + - either the this is explicit: eg C.this.XXX. In this case, the position is [C.this], but + we want to put the symbol on the C, so around id + - either it is not explicit (eg a.foo). We want to put the symbol only around the a. + Distinguishing between the two is easy. If the sourcecode between [pos.start; pos.end] ends + with a 'this', then we're in the first case, otherwise the second + */ + var rangeThis = createRange(tree.pos) + if (sourceCode.peek(tree.pos.start, tree.pos.end).endsWith("this")) { + rangeThis = createRange(tree.pos.startLine, tree.pos.startColumn, tree.symbol.trueName.length) } - super.traverseTree(body) + addOccurenceTree(tree, + s.SymbolOccurrence.Role.REFERENCE, + rangeThis) + } + + case Term.Super(_, Some(id)) => { + addOccurence(classStacks.head, + s.SymbolOccurrence.Role.DEFINITION, + createRange(id.pos)) + super.traverseTree(tree) } case Term.Select(qualifier, _) => { - val range = rangeExclude(tree.pos, qualifier.pos) - addOccurenceTree(tree, s.SymbolOccurrence.Role.REFERENCE, range) + var range = rangeSelect(tree.symbol.trueName, tree.pos) + + /* This branch deals with select of a `this`. Their is two options: + - The select of this is explicit (`C.this`). To know if we are in this case we + check if the end of our position in the sourceCode corresponds to a "this". + - The select is implicit and was compiler generated. We will force to add it if and only if + the qualifier itself was user created + */ + var shouldForceAdd = false + if (tree.symbol.trueName == "") { + if (tree.pos.start == tree.pos.end && sourceCode.peek(tree.pos.start - 5, tree.pos.start - 1) == "this") { + range = createRange(0, tree.pos.start - 5, 4) + shouldForceAdd = true + } else { + range = createRange(tree.pos.endLine, tree.pos.endColumn) + shouldForceAdd = qualifier.isUserCreated + } + } + /* We do not forget to disable the [isAssignedTerm] flag when exploring the qualifier + of our select*/ + val temp = isAssignedTerm + isAssignedTerm = false super.traverseTree(tree) + isAssignedTerm = temp + + /* If we selected a term x which is a mutable variable without a private local flag we want + to record a call to the function x_= instead. We set the corresponding flag on*/ + val isMutableAssignement = isAssignedTerm && tree.symbol.flags.is(Flags.Mutable) && !tree.symbol.flags.is(Flags.PrivateLocal) + addOccurenceTree(tree, s.SymbolOccurrence.Role.REFERENCE, range, shouldForceAdd, isMutableAssignement) } - 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)) + case Term.Ident(name) => { + addOccurenceTree(tree, + s.SymbolOccurrence.Role.REFERENCE, + createRange(tree.pos.startLine, tree.pos.startColumn, tree.symbol.trueName.length)) + + super.traverseTree(tree) + } + + case Term.Inlined(Some(c), b, d) => { + /* In theory files should be compiled with -Yno-inline before running semanticdb. + If this is not the case, here is a fallback to heuristically determine which predefFunction + corresponds to an inlined term. + + We peek the character below the inline node. + If it is an "l" (for locally), then we have a locally predef call + If it is an "i" (for implicitly") then it is an implicitly call + If it is an "a" it is an assert + */ + def getPredefFunction(pos: Int): String = { + sourceCode.peek(pos, pos+1) match { + case "l" => "locally" + case "i" => "implicitly" + case "a" => "assert" + } } + val parentSymbol = iterateParent(c.symbol) + if (parentSymbol == "dotty/DottyPredef.") { + val pos = c.pos + val function = getPredefFunction(pos.start) + val range = createRange(pos.startLine, pos.startColumn, pos.startLine, function.length) + addOccurencePredef(parentSymbol, function, range) + } + super.traverseTree(tree) } + case PackageClause(_) => - if (!package_definitions(tree.symbol.name)) { + val key = (tree.symbol.trueName, tree.pos.start) + if (!packageDefinitions(key)) { addOccurenceTree(tree, - s.SymbolOccurrence.Role.REFERENCE, - range(tree, tree.pos, tree.symbol.name)) - package_definitions += tree.symbol.name + s.SymbolOccurrence.Role.DEFINITION, + createRange(tree.pos.startLine, tree.pos.startColumn + "package ".length, tree.symbol.trueName.length)) + packageDefinitions += key } super.traverseTree(tree) @@ -285,7 +1052,6 @@ class SemanticdbConsumer extends TastyConsumer { } } - Traverser.traverseTree(root)(reflect.rootContext) } diff --git a/semanticdb/src/dotty/semanticdb/SourceFile.scala b/semanticdb/src/dotty/semanticdb/SourceFile.scala new file mode 100644 index 000000000000..c53579b6e338 --- /dev/null +++ b/semanticdb/src/dotty/semanticdb/SourceFile.scala @@ -0,0 +1,71 @@ +package dotty.semanticdb + +import scala.io.Source +import scala.math._ +import dotty.tools.dotc.util.SourceFile + +class SourceFile(path: java.nio.file.Path) { + private val sourceCode = Source.fromFile(path.toFile).mkString + private val sourceFile = SourceFile.virtual(path.toString, sourceCode) + + def content() = sourceCode + + def offsetToLine(offset: Int): Int = sourceFile.offsetToLine(offset) + + def lineToOffset(offset: Int): Int = sourceFile.lineToOffset(offset) + + def peek(start: Int, end: Int) : String = + sourceCode.substring(max(start, 0), min(end, sourceCode.length - 1)) + + def firstOccurrenceLetter(letter: Char, start : Int) : Int = { + var pos = start + while (pos < sourceCode.length && sourceCode(pos) != letter) { + val nextPos = nextCharacterSkipComments(pos) + if (nextPos == pos) + pos = nextPos + 1 + else + pos = nextPos + } + return pos + } + + def nextCharacterSkipComments(start : Int): Int = { + def aux(start : Int) : Int = { + var i = start + if (i+2 <= sourceCode.length && sourceCode.substring(i, i+2) == "//") { + while (i < sourceCode.length && sourceCode(i) != '\n') + i += 1 + return i+1 + } else if (i+2 <= sourceCode.length && sourceCode.substring(i, i+2) == "/*") { + var nestedCount = 0 + i += 2 + while (i + 2 <= sourceCode.length && + !(sourceCode.substring(i, i+2) == "*/" && + nestedCount == 0)) { + val s = sourceCode.substring(i, i+2) + if (s == "/*") { + nestedCount += 1 + i += 1 + } + if (s == "*/") { + nestedCount -= 1 + i += 1 + } + i += 1 + } + return i+2 + } else { + while (i < sourceCode.length && sourceCode(i).isWhitespace) + i += 1 + return i + } + } + var previous = start + var next = aux(previous) + while (previous != next) { + previous = next + next = aux(previous) + } + return previous + } +} \ No newline at end of file diff --git a/semanticdb/src/dotty/semanticdb/TastyScalaFileInferrer.scala b/semanticdb/src/dotty/semanticdb/TastyScalaFileInferrer.scala new file mode 100644 index 000000000000..15b13a41404d --- /dev/null +++ b/semanticdb/src/dotty/semanticdb/TastyScalaFileInferrer.scala @@ -0,0 +1,49 @@ +package dotty.semanticdb + +import scala.tasty.Reflection + +import scala.meta.internal.{semanticdb => s} +import scala.tasty.Reflection +import scala.tasty.file.TastyConsumer +import java.lang.reflect.InvocationTargetException + +class TastyScalaFileInferrer extends TastyConsumer { + /* Visitor over a tasty tree. + Aims at finding the scala file from where this tree originated. + */ + + /* If a scala file was found sourcePath is Some(scalaFile), + Otherwise None */ + var sourcePath: 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 + sourcePath = Some(path) + case x => super.traverseTree(tree) + } + true + } + } + case _ => { + // If we already found a sourcePath in this tasty file, + // we abort our search here to avoid spending too much time here + if (sourcePath == None) + super.traverseTree(tree) + else + () + } + } + } + ChildTraverser.traverseTree(root)(reflect.rootContext) + } +} diff --git a/semanticdb/src/dotty/semanticdb/Utils.scala b/semanticdb/src/dotty/semanticdb/Utils.scala new file mode 100644 index 000000000000..634e7ad9b80b --- /dev/null +++ b/semanticdb/src/dotty/semanticdb/Utils.scala @@ -0,0 +1,85 @@ +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 + +object Utils { + /** 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) + } + + /** List all tasty files occuring in the folder f or one of its subfolders */ + def recursiveListFiles(f: File, prefix : String = ""): Array[File] = { + val pattern = (".*" + prefix + ".*\\.tasty").r + val files = f.listFiles + val folders = files.filter(_.isDirectory) + val tastyfiles = files.filter(_.toPath.toString match { + case pattern(x: _*) => true + case _ => false + }) + tastyfiles ++ folders.flatMap(recursiveListFiles(_, prefix)) + } + + /** Returns a mapping from *.scala file to a list of tasty files. */ + def getTastyFiles(classPath: Path, prefix : String = ""): HashMap[String, List[Path]] = { + val sourceToTasty: HashMap[String, List[Path]] = HashMap() + val tastyfiles = recursiveListFiles(classPath.toFile(), prefix) + tastyfiles.map(tastyPath => { + val (classpath, classname) = getClasspathClassname(tastyPath.toPath()) + // We add an exception here to avoid crashing if we encountered + // a bad tasty file + try { + val inspecter = new TastyScalaFileInferrer + ConsumeTasty(classpath, classname :: Nil, inspecter) + inspecter.sourcePath.foreach( + source => + sourceToTasty += + (source -> (tastyPath + .toPath().toAbsolutePath :: sourceToTasty.getOrElse(source, Nil)))) + } catch { + case _: InvocationTargetException => () + } + }) + sourceToTasty + } + + /* + Returns the list of names of class defined inside the scala file [scalaFile] + extracted from the compilation artifacts found in [classPath]. + */ + def getClassNames(classPath: Path, scalaFile: Path, prefix : String = ""): List[String] = { + val tastyFiles = getTastyFiles(classPath.toAbsolutePath, prefix) + getClassNamesCached(scalaFile, tastyFiles) + } + + def getClassNamesCached(scalaFile: Path, allFiles : HashMap[String, List[Path]]): List[String] = { + val tastyFiles = + allFiles + .getOrElse(scalaFile.toString, Nil) + + val tastyClasses = tastyFiles.map(getClasspathClassname) + val (_, classnames) = tastyClasses.unzip + return classnames + } +} \ No newline at end of file diff --git a/semanticdb/test/dotty/semanticdb/Semanticdbs.scala b/semanticdb/test/dotty/semanticdb/Semanticdbs.scala index c237a7db4deb..762673222ff5 100644 --- a/semanticdb/test/dotty/semanticdb/Semanticdbs.scala +++ b/semanticdb/test/dotty/semanticdb/Semanticdbs.scala @@ -81,8 +81,10 @@ object Semanticdbs { **/ def printTextDocument(doc: s.TextDocument): String = { val sb = new StringBuilder - val occurrences = doc.occurrences.sorted val sourceFile = SourceFile.virtual(doc.uri, doc.text) + implicit val occurrenceOrdering: Ordering[s.SymbolOccurrence] = + buildOccurrenceOrdering(sourceFile) + val occurrences = doc.occurrences.sorted var offset = 0 occurrences.foreach { occ => val range = occ.range.get @@ -98,28 +100,28 @@ object Semanticdbs { } /** Sort symbol occurrences by their start position. */ - implicit val occurrenceOrdering: Ordering[s.SymbolOccurrence] = + def buildOccurrenceOrdering(sourceFile: SourceFile): Ordering[s.SymbolOccurrence] = { new Ordering[s.SymbolOccurrence] { + def rangeToTuple(r : s.Range): (Int, Int) = { + val start = sourceFile.lineToOffset(r.startLine) + r.startCharacter + val end = sourceFile.lineToOffset(r.endLine) + r.endCharacter + (start, end) + } + override def compare(x: s.SymbolOccurrence, y: s.SymbolOccurrence): Int = { if (x.range.isEmpty) 0 else if (y.range.isEmpty) 0 else { - val a = x.range.get - val b = y.range.get - val byLine = Integer.compare( - a.startLine, - b.startLine - ) - if (byLine != 0) { - byLine + val (as, ae) = rangeToTuple(x.range.get) + val (bs, be) = rangeToTuple(y.range.get) + val byStart = Integer.compare(as, bs) + if (byStart != 0) { + byStart } else { - val byCharacter = Integer.compare( - a.startCharacter, - b.startCharacter - ) - byCharacter + Integer.compare(ae, be) } } } } + } } diff --git a/semanticdb/test/dotty/semanticdb/Tests.scala b/semanticdb/test/dotty/semanticdb/Tests.scala index ab38ff7308ac..480f38f5ea69 100644 --- a/semanticdb/test/dotty/semanticdb/Tests.scala +++ b/semanticdb/test/dotty/semanticdb/Tests.scala @@ -14,136 +14,143 @@ 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 - } +import dotty.semanticdb.Scala._ +import dotty.tools.dotc.util.SourceFile +import scala.math._ + +class Tests { + + def distance(r1: s.Range, sourceFile: SourceFile)(r2: s.Range): Int = { + val s1 = sourceFile.lineToOffset(max(r1.startLine, 0)) + r1.startCharacter + val s2 = sourceFile.lineToOffset(max(r2.startLine, 0)) + r2.startCharacter + val e1 = sourceFile.lineToOffset(max(r1.endLine, 0)) + r1.endCharacter + val e2 = sourceFile.lineToOffset(max(r2.endLine, 0)) + r2.endCharacter + max(abs(s1 - s2), abs(e1 - e2)) + } + + /* This is a most powerfull way to compare two lists of occurences + than just to print the diff. The diff will fail local symbols have not + the same numer (even if their is no conflicts) and will also fail for symbols + with small differences. + The goal here is to have a method returning true if the tasty occurences and + the scala occurences are the "same" (no local conflicts, positions can be slightly + different). */ + def compareOccurrences(tastyOccurrences: Seq[s.SymbolOccurrence], + scalaOccurrences: Seq[s.SymbolOccurrence], + sourceCode: String): Boolean = { + val sourceFile = SourceFile.virtual("", sourceCode) + val symbols = tastyOccurrences.groupBy(_.symbol) + val localTastyToScala = HashMap[String, String]() + val localScalaToTasty = HashMap[String, String]() + val translator = HashMap[(s.Range, String), s.SymbolOccurrence]() + + /* Check if we can translate the symbol [from] to the symbol [to] + True if their is no local clash (ex if we know local2 -> local3 then local2 clash with local4) + [from] is in tasty space, [to] in scala space + */ + def checkIfTranslatableSymbol(from: String, to: String): Boolean = { + if (from.isLocal != to.isLocal) { + false + } else { + if (from.isLocal) { + if (localTastyToScala.getOrElse(from, to) == to && + localScalaToTasty.getOrElse(to, from) == from) { + localTastyToScala += (from -> to) + localScalaToTasty += (to -> from) + true + } else { + false } - case _ => { - if (source_path == None) - super.traverseTree(tree) - else - () + } else { + true + } + } + } + + /* If we do not have the same number of occurrences in both lists we know we failed + the test */ + if (tastyOccurrences.length != scalaOccurrences.length) { + false + } else { + /* We associate every scala occurrence to the nearest tasty occurence with the + same name (if it is a global symbol) or local symbol */ + scalaOccurrences.forall(occurrence => { + if (occurrence.symbol.isLocal || + symbols.contains(localScalaToTasty.getOrElse(occurrence.symbol, + occurrence.symbol))) { + val siblings = + if (occurrence.symbol.isLocal) tastyOccurrences + else symbols(occurrence.symbol) + + val nearest = siblings.minBy(c => + distance(occurrence.range.get, sourceFile)(c.range.get)) + + /* If these two symbols can be translated, meaning: + - [nearest] was not associated with any other scala occurrence + - if [occurrence] is a local symbol it does not clash with [nearest] + - they are "near" in the source code */ + if (!checkIfTranslatableSymbol(nearest.symbol, occurrence.symbol) || + translator.contains((nearest.range.get, nearest.symbol)) || + distance(occurrence.range.get, sourceFile)(nearest.range.get) > 5) { + println( + checkIfTranslatableSymbol(nearest.symbol, occurrence.symbol)) + false + } else { + if (!occurrence.symbol.isLocal) + translator += ((nearest.range.get, nearest.symbol) -> occurrence) + true } + } else { + false } + }) } - ChildTraverser.traverseTree(root)(reflect.rootContext) } -} -class Tests { + final def tastyClassDirectory = { + val root = "out/bootstrap/dotty-semanticdb/" + val files = Paths.get(root).toFile().listFiles + val scalaFolderReg = """scala-(\d+)\.(\d+)""".r + val (_, _, path) = files + .collect(file => + file.getName match { + case scalaFolderReg(major, minor) => (major, minor, file.getName) + }) + .max + Paths.get(root, path, "test-classes") + } - // TODO: update scala-0.10 on version change (or resolve automatically) - final def tastyClassDirectory = - "out/bootstrap/dotty-semanticdb/scala-0.11/test-classes" - val sourceroot = Paths.get("semanticdb", "input").toAbsolutePath + 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 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 } - /** 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) + final def allTastyFiles = Utils.getTastyFiles(tastyClassDirectory, "example") - 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 + /** Returns the SemanticDB for this Scala source file. */ + def getTastySemanticdb(classPath: Path, scalaFile: Path): s.TextDocument = { + val classNames = Utils.getClassNamesCached(scalaFile, allTastyFiles) + val sdbconsumer = new SemanticdbConsumer(scalaFile) - val _ = ConsumeTasty(classpaths.head, classnames, sdbconsumer) - sdbconsumer.toSemanticdb(scalac.text) - } + val _ = ConsumeTasty(classPath.toString, classNames, sdbconsumer) + sdbconsumer.toSemanticdb() } /** 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 = getTastySemanticdb(path) + val tasty = getTastySemanticdb(tastyClassDirectory, path) val obtained = Semanticdbs.printTextDocument(tasty) val expected = Semanticdbs.printTextDocument(scalac) - assertNoDiff(obtained, expected) + if (!compareOccurrences(tasty.occurrences, scalac.occurrences, scalac.text)) + assertNoDiff(obtained, expected) } /** Fails the test with a pretty diff if there obtained is not the same as expected */ @@ -173,15 +180,44 @@ class Tests { } } - + @Test def testAccess(): Unit = checkFile("example/Access.scala") + @Test def testAdvanced(): Unit = checkFile("example/Advanced.scala") + @Test def testAnonymous(): Unit = checkFile("example/Anonymous.scala") + @Test def testClasses(): Unit = checkFile("example/Classes.scala") + @Test def testEmpty(): Unit = checkFile("example/Empty.scala") + @Test def testEmptyObject(): Unit = checkFile("example/EmptyObject.scala") @Test def testExample(): Unit = checkFile("example/Example.scala") - // TODO: add more tests - - def testOutput(className: String, expected: String): Unit = { - val out = new StringBuilder - ConsumeTasty(tastyClassDirectory, List(className), new SemanticdbConsumer { - override def println(x: Any): Unit = out.append(x).append(";") - }) - assertEquals(expected, out.result()) - } + @Test def testExample2(): Unit = checkFile("example/Example2.scala") + @Test def testExclude(): Unit = checkFile("example/Exclude.scala") + @Test def testFlags(): Unit = checkFile("example/Flags.scala") + @Test def testIssue1749(): Unit = checkFile("example/Issue1749.scala") + @Test def testLocalFile(): Unit = checkFile("example/local-file.scala") + @Test def testLocals(): Unit = checkFile("example/Locals.scala") + //deactivated @Test def testMacroAnnotations(): Unit = checkFile("example/MacroAnnotations.scala") + @Test def testMethods(): Unit = checkFile("example/Methods.scala") + @Test def testMultiArguments(): Unit = + checkFile("example/MultiArguments.scala") + @Test def testObjects(): Unit = checkFile("example/Objects.scala") + @Test def testOverrides(): Unit = checkFile("example/Overrides.scala") + @Test def testPrefixes(): Unit = checkFile("example/Prefixes.scala") + @Test def testSelfs(): Unit = checkFile("example/Selfs.scala") + @Test def testSelfUse(): Unit = checkFile("example/SelfUse.scala") + @Test def testTraits(): Unit = checkFile("example/Traits.scala") + @Test def testTypes(): Unit = checkFile("example/Types.scala") + @Test def testTypesAnnotations(): Unit = + checkFile("example/TypesAnnotations.scala") + @Test def testVals(): Unit = checkFile("example/Vals.scala") + @Test def testDependantModule(): Unit = + checkFile("example/DependantModule.scala") + @Test def testNew(): Unit = checkFile("example/New.scala") + @Test def testIgnoredSymbol(): Unit = checkFile("example/IgnoredSymbol.scala") + @Test def testCase(): Unit = checkFile("example/Case.scala") + @Test def testApply(): Unit = checkFile("example/Apply.scala") + @Test def testMethodUsages(): Unit = checkFile("example/MethodUsages.scala") + @Test def testSuper(): Unit = checkFile("example/Super.scala") + @Test def testTypeBug(): Unit = checkFile("example/TypeBug.scala") + @Test def testSynthetic(): Unit = checkFile("example/Synthetic.scala") + @Test def testBinaryOp(): Unit = checkFile("example/BinaryOp.scala") + @Test def testDottyPredef(): Unit = checkFile("example/DottyPredef.scala") + } diff --git a/tests/pos-with-compiler/tasty/definitions.scala b/tests/pos-with-compiler/tasty/definitions.scala index 40363f4e8eeb..f5358aee3d4a 100644 --- a/tests/pos-with-compiler/tasty/definitions.scala +++ b/tests/pos-with-compiler/tasty/definitions.scala @@ -246,7 +246,7 @@ object definitions { def isMutable: Boolean // when used on a ValDef: a var def isLabel: Boolean // method generated as a label def isFieldAccessor: Boolean // a getter or setter - def isCaseAcessor: Boolean // getter for class parameter + def isCaseAccessor: Boolean // getter for class parameter def isCovariant: Boolean // type parameter marked “+” def isContravariant: Boolean // type parameter marked “-” def isScala2X: Boolean // Imported from Scala2.x