Skip to content

compile dotty with Ysemanticdb #8983

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
May 19, 2020
59 changes: 30 additions & 29 deletions compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class ExtractSemanticDB extends Phase:
val occurrences = new mutable.ListBuffer[SymbolOccurrence]()

/** The extracted symbol infos */
val symbolInfos = new mutable.HashSet[SymbolInformation]()
val symbolInfos = new mutable.ListBuffer[SymbolInformation]()

/** A cache of localN names */
val localNames = new mutable.HashSet[String]()
Expand Down Expand Up @@ -104,7 +104,7 @@ class ExtractSemanticDB extends Phase:
|| sym == defn.Any_typeCast
|| qualifier.exists(excludeQual)

private def traverseAnnotsOf(sym: Symbol)(using Context): Unit =
private def traverseAnnotsOfDefinition(sym: Symbol)(using Context): Unit =
for annot <- sym.annotations do
if annot.tree.span.exists
&& annot.tree.span.hasLength
Expand All @@ -114,24 +114,19 @@ class ExtractSemanticDB extends Phase:

override def traverse(tree: Tree)(using Context): Unit =

inline def traverseCtorParamTpt(ctorSym: Symbol, tpt: Tree): Unit =
val tptSym = tpt.symbol
if tptSym.owner == ctorSym
val found = matchingMemberType(tptSym, ctorSym.owner)
if tpt.span.hasLength
registerUseGuarded(None, found, tpt.span)
else
traverse(tpt)

traverseAnnotsOf(tree.symbol)
tree match
case tree: DefTree if tree.symbol.exists =>
traverseAnnotsOfDefinition(tree.symbol)
case _ =>
()

tree match
case tree: PackageDef =>
if !excludeDef(tree.pid.symbol)
&& tree.pid.span.hasLength
tree.pid match
case tree @ Select(qual, name) =>
registerDefinition(tree.symbol, adjustSpanToName(tree.span, qual.span, name), Set.empty)
registerDefinition(tree.symbol, adjustSpanToName(tree.span, qual.span, name, tree.source), Set.empty)
traverse(qual)
case tree => registerDefinition(tree.symbol, tree.span, Set.empty)
tree.stats.foreach(traverse)
Expand Down Expand Up @@ -180,8 +175,9 @@ class ExtractSemanticDB extends Phase:
case tree: Template =>
val ctorSym = tree.constr.symbol
if !excludeDef(ctorSym)
traverseAnnotsOfDefinition(ctorSym)
registerDefinition(ctorSym, tree.constr.span, Set.empty)
ctorParams(tree.constr.vparamss, tree.body)(traverseCtorParamTpt(ctorSym, _))
ctorParams(tree.constr.vparamss, tree.body)
for parent <- tree.parentsOrDerived if parent.span.hasLength do
traverse(parent)
val selfSpan = tree.self.span
Expand All @@ -206,7 +202,7 @@ class ExtractSemanticDB extends Phase:
val lhs = tree.lhs.symbol
val setter = lhs.matchingSetter.orElse(lhs)
tree.lhs match
case tree @ Select(qual, name) => registerUse(setter, adjustSpanToName(tree.span, qual.span, name))
case tree @ Select(qual, name) => registerUse(setter, adjustSpanToName(tree.span, qual.span, name, tree.source))
case tree => registerUse(setter, tree.span)
traverseChildren(tree.lhs)
traverse(tree.rhs)
Expand All @@ -217,7 +213,7 @@ class ExtractSemanticDB extends Phase:
case tree: Select =>
val qualSpan = tree.qualifier.span
val sym = tree.symbol.adjustIfCtorTyparam
registerUseGuarded(tree.qualifier.symbol.ifExists, sym, adjustSpanToName(tree.span, qualSpan, tree.name))
registerUseGuarded(tree.qualifier.symbol.ifExists, sym, adjustSpanToName(tree.span, qualSpan, tree.name, tree.source))
if qualSpan.exists && qualSpan.hasLength then
traverse(tree.qualifier)
case tree: Import =>
Expand Down Expand Up @@ -302,12 +298,15 @@ class ExtractSemanticDB extends Phase:
else
decls0
end decls
val alts = decls.filter(_.is(Method)).toList.reverse
alts match
case notSym :: rest if sym != notSym =>
val idx = rest.indexOf(sym).ensuring(_ >= 0)
b.append('+').append(idx + 1)
case _ =>
val alts = decls.filter(_.isOneOf(Method | Mutable)).toList.reverse
def find(filter: Symbol => Boolean) = alts match
case notSym :: rest if !filter(notSym) =>
val idx = rest.indexWhere(filter).ensuring(_ >= 0)
b.append('+').append(idx + 1)
case _ =>
end find
val sig = sym.signature
find(_.signature == sig)

def addDescriptor(sym: Symbol): Unit =
if sym.is(ModuleClass) then
Expand Down Expand Up @@ -335,14 +334,16 @@ class ExtractSemanticDB extends Phase:
* the same starting position have the same index.
*/
def localIdx(sym: Symbol)(using Context): Int =
def startPos(sym: Symbol) =
if sym.span.exists then sym.span.start else -1
def computeLocalIdx(): Int =
symsAtOffset(sym.span.start).find(_.name == sym.name) match
symsAtOffset(startPos(sym)).find(_.name == sym.name) match
case Some(other) => localIdx(other)
case None =>
val idx = nextLocalIdx
nextLocalIdx += 1
locals(sym) = idx
symsAtOffset(sym.span.start) += sym
symsAtOffset(startPos(sym)) += sym
idx
locals.getOrElseUpdate(sym, computeLocalIdx())

Expand Down Expand Up @@ -502,7 +503,7 @@ class ExtractSemanticDB extends Phase:
}).toMap
end findGetters

private def adjustSpanToName(span: Span, qualSpan: Span, name: Name)(using Context) =
private def adjustSpanToName(span: Span, qualSpan: Span, name: Name, source: SourceFile) =
val end = span.end
val limit = qualSpan.end
val start =
Expand Down Expand Up @@ -559,20 +560,20 @@ class ExtractSemanticDB extends Phase:
case _ =>
symkinds.toSet

private inline def ctorParams(
vparamss: List[List[ValDef]], body: List[Tree])(traverseTpt: => Tree => Unit)(using Context): Unit =
private def ctorParams(
vparamss: List[List[ValDef]], body: List[Tree])(using Context): Unit =
@tu lazy val getters = findGetters(vparamss.flatMap(_.map(_.name)).toSet, body)
for
vparams <- vparamss
vparam <- vparams
do
traverseAnnotsOf(vparam.symbol)
if !excludeSymbol(vparam.symbol)
traverseAnnotsOfDefinition(vparam.symbol)
val symkinds =
getters.get(vparam.name).fold(SymbolKind.emptySet)(getter =>
if getter.mods.is(Mutable) then SymbolKind.VarSet else SymbolKind.ValSet)
registerSymbol(vparam.symbol, symbolName(vparam.symbol), symkinds)
traverseTpt(vparam.tpt)
traverse(vparam.tpt)

object ExtractSemanticDB:
import java.nio.file.Path
Expand Down
7 changes: 4 additions & 3 deletions compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class CompilationTests extends ParallelTesting {
compileFilesInDir("tests/new", defaultOptions),
compileFilesInDir("tests/pos-scala2", scala2CompatMode),
compileFilesInDir("tests/pos-custom-args/erased", defaultOptions.and("-Yerased-terms")),
compileFilesInDir("tests/pos-custom-args/semanticdb", defaultOptions.and("-Ysemanticdb")),
compileFilesInDir("tests/pos", defaultOptions),
compileFilesInDir("tests/pos-deep-subtype", allowDeepSubtypes),
compileFile(
Expand Down Expand Up @@ -201,9 +202,9 @@ class CompilationTests extends ParallelTesting {
).checkCompile()
}

/** The purpose of this test is two-fold, being able to compile dotty
/** The purpose of this test is three-fold, being able to compile dotty
* bootstrapped, and making sure that TASTY can link against a compiled
* version of Dotty
* version of Dotty, and compiling the compiler using the SemanticDB generation
*/
@Test def tastyBootstrap: Unit = {
implicit val testGroup: TestGroup = TestGroup("tastyBootstrap/tests")
Expand All @@ -227,7 +228,7 @@ class CompilationTests extends ParallelTesting {
Properties.compilerInterface, Properties.scalaLibrary, Properties.scalaAsm,
Properties.dottyInterfaces, Properties.jlineTerminal, Properties.jlineReader,
).mkString(File.pathSeparator),
Array("-Ycheck-reentrant", "-Yemit-tasty-in-class", "-language:postfixOps")
Array("-Ycheck-reentrant", "-Yemit-tasty-in-class", "-language:postfixOps", "-Ysemanticdb")
)

val libraryDirs = List(Paths.get("library/src"), Paths.get("library/src-bootstrapped"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ class SemanticdbTests:
println(s"""[${red("error")}] check file ${blue(expect.toString)} does not match generated.
|If you meant to make a change, replace the expect file by:
| mv ${expect.resolveSibling("" + expect.getFileName + ".out")} $expect
|inspect with:
| diff $expect ${expect.resolveSibling("" + expect.getFileName + ".out")}
|Or else update all expect files with
| sbt 'dotty-compiler-bootstrapped/test:runMain dotty.tools.dotc.semanticdb.updateExpect'""".stripMargin)
Files.walk(target).sorted(Comparator.reverseOrder).forEach(Files.delete)
Expand Down
1 change: 1 addition & 0 deletions compiler/test/dotty/tools/vulpix/TestConfiguration.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ object TestConfiguration {
"-Yno-deep-subtypes",
"-Yno-double-bindings",
"-Yforce-sbt-phases",
"-Ysemanticdb",
"-Xverify-signatures"
)

Expand Down
12 changes: 12 additions & 0 deletions tests/pos-custom-args/semanticdb/inline-unapply/App_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

object Test {
def main(args: Array[String]): Unit = {
0 match
case Succ(n) => ???
case _ =>

2 match
case Succ(n) => assert(n == 1)
}

}
8 changes: 8 additions & 0 deletions tests/pos-custom-args/semanticdb/inline-unapply/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import scala.quoted._

object Succ:

inline def unapply(n: Int): Option[Int] = ${ impl('n) }

private def impl(n: Expr[Int])(using QuoteContext): Expr[Option[Int]] =
'{ if $n == 0 then None else Some($n - 1)}
5 changes: 5 additions & 0 deletions tests/pos-custom-args/semanticdb/macro-pos/example_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import quoted._

object CodeImpl {
def codeExpr(using qctx: QuoteContext): Expr[String] = '{""}
}
10 changes: 10 additions & 0 deletions tests/pos-custom-args/semanticdb/macro-pos/example_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import quoted._

object TestImpl {
transparent inline def fun (inline arg: String): String =
/*
Pad out the file
Lorem ipsum dolor sit amet consectetur adipiscing elit tincidunt id augue, facilisi dignissim nibh litora lectus quam senectus fringilla molestie sollicitudin, nunc ullamcorper auctor turpis integer netus fermentum mattis magna. Nullam potenti diam tellus bibendum odio tristique felis, pharetra posuere at imperdiet suspendisse aenean, eu lobortis sapien eleifend aptent sociosqu.
*/
${ CodeImpl.codeExpr }
}
5 changes: 5 additions & 0 deletions tests/pos-custom-args/semanticdb/macro-pos/example_3.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
object Test {

def test = TestImpl.fun("")

}
23 changes: 23 additions & 0 deletions tests/semanticdb/expect/Classes.expect.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,29 @@ class C8/*<-classes::C8#*/(private[this] val x/*<-classes::C8#x.*/: Int/*->scala

class C9/*<-classes::C9#*/(private[this] var x/*<-classes::C9#x().*/: Int/*->scala::Int#*/)

class C10/*<-classes::C10#*/(s/*<-classes::C10#s.*/: => String/*->scala::Predef.String#*/)

class C11/*<-classes::C11#*/ {
def foo/*<-classes::C11#foo().*/: Int/*->scala::Int#*/ = macro ???/*->scala::Predef.`???`().*/
inline def foo/*<-classes::C11#foo().*/: Int/*->scala::Int#*/ = ???/*->scala::Predef.`???`().*/
}

class C12/*<-classes::C12#*/ {

class Context/*<-classes::C12#Context#*/: // Dummy scala.reflect.macros.Context
type Expr/*<-classes::C12#Context#Expr#*/[T/*<-classes::C12#Context#Expr#[T]*/]

def foo1/*<-classes::C12#foo1().*/(x/*<-classes::C12#foo1().(x)*/: Int/*->scala::Int#*/): Int/*->scala::Int#*/ = macro foo1Impl/*->classes::C12#foo1Impl().*/
def foo1/*<-classes::C12#foo1().*/(x/*<-classes::C12#foo1().(x)*/: Int/*->scala::Int#*/): Int/*->scala::Int#*/ = ???/*->scala::Predef.`???`().*/

def foo2/*<-classes::C12#foo2().*/(x/*<-classes::C12#foo2().(x)*/: Int/*->scala::Int#*/, y/*<-classes::C12#foo2().(y)*/: String/*->scala::Predef.String#*/): Int/*->scala::Int#*/ = macro foo2Impl/*->classes::C12#foo2Impl().*/
def foo2/*<-classes::C12#foo2().*/(x/*<-classes::C12#foo2().(x)*/: Int/*->scala::Int#*/, y/*<-classes::C12#foo2().(y)*/: String/*->scala::Predef.String#*/): Int/*->scala::Int#*/ = ???/*->scala::Predef.`???`().*/

def foo1Impl/*<-classes::C12#foo1Impl().*/(context/*<-classes::C12#foo1Impl().(context)*/: Context/*->classes::C12#Context#*/)(x/*<-classes::C12#foo1Impl().(x)*/: context/*->classes::C12#foo1Impl().(context)*/.Expr/*->classes::C12#Context#Expr#*/[Int/*->scala::Int#*/]): context/*->classes::C12#foo1Impl().(context)*/.Expr/*->classes::C12#Context#Expr#*/[Int/*->scala::Int#*/] = ???/*->scala::Predef.`???`().*/
def foo2Impl/*<-classes::C12#foo2Impl().*/(context/*<-classes::C12#foo2Impl().(context)*/: Context/*->classes::C12#Context#*/)(x/*<-classes::C12#foo2Impl().(x)*/: context/*->classes::C12#foo2Impl().(context)*/.Expr/*->classes::C12#Context#Expr#*/[Int/*->scala::Int#*/], y/*<-classes::C12#foo2Impl().(y)*/: context/*->classes::C12#foo2Impl().(context)*/.Expr/*->classes::C12#Context#Expr#*/[String/*->scala::Predef.String#*/]): context/*->classes::C12#foo2Impl().(context)*/.Expr/*->classes::C12#Context#Expr#*/[Int/*->scala::Int#*/] = ???/*->scala::Predef.`???`().*/

}

object N/*<-classes::N.*/ {
val anonClass/*<-classes::N.anonClass.*/ = new C7/*->classes::C7#*/(42) {
val local/*<-local1*/ = ???/*->scala::Predef.`???`().*/
Expand Down
23 changes: 23 additions & 0 deletions tests/semanticdb/expect/Classes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,29 @@ class C8(private[this] val x: Int)

class C9(private[this] var x: Int)

class C10(s: => String)

class C11 {
def foo: Int = macro ???
inline def foo: Int = ???
}

class C12 {

class Context: // Dummy scala.reflect.macros.Context
type Expr[T]

def foo1(x: Int): Int = macro foo1Impl
def foo1(x: Int): Int = ???

def foo2(x: Int, y: String): Int = macro foo2Impl
def foo2(x: Int, y: String): Int = ???

def foo1Impl(context: Context)(x: context.Expr[Int]): context.Expr[Int] = ???
def foo2Impl(context: Context)(x: context.Expr[Int], y: context.Expr[String]): context.Expr[Int] = ???

}

object N {
val anonClass = new C7(42) {
val local = ???
Expand Down
4 changes: 2 additions & 2 deletions tests/semanticdb/expect/Enums.expect.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ object Enums/*<-_empty_::Enums.*/:
suit/*->_empty_::Enums.Suits.isRed().(suit)*/ ==/*->scala::Any#`==`().*/ Hearts/*->_empty_::Enums.Suits.Hearts.*/ ||/*->scala::Boolean#`||`().*/ suit/*->_empty_::Enums.Suits.isRed().(suit)*/ ==/*->scala::Any#`==`().*/ Diamonds/*->_empty_::Enums.Suits.Diamonds.*/

def (suit: /*<-_empty_::Enums.Suits.isBlack().*//*<-_empty_::Enums.Suits.isBlack().(suit)*/Suits/*->_empty_::Enums.Suits#*/).isBlack: Boolean/*->scala::Boolean#*/ = suit/*->_empty_::Enums.Suits.isBlack().(suit)*/ match
case Spades/*->_empty_::Enums.Suits.Spades.*/ | Diamonds/*->_empty_::Enums.Suits.Diamonds.*/ => true
case _ => false
case Spades/*->_empty_::Enums.Suits.Spades.*/ | Clubs/*->_empty_::Enums.Suits.Clubs.*/ => true
case _ => false

enum WeekDays/*<-_empty_::Enums.WeekDays#*/:
case Monday/*<-_empty_::Enums.WeekDays.Monday.*/
Expand Down
4 changes: 2 additions & 2 deletions tests/semanticdb/expect/Enums.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ object Enums:
suit == Hearts || suit == Diamonds

def (suit: Suits).isBlack: Boolean = suit match
case Spades | Diamonds => true
case _ => false
case Spades | Clubs => true
case _ => false

enum WeekDays:
case Monday
Expand Down
6 changes: 6 additions & 0 deletions tests/semanticdb/expect/MethodUsages.expect.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,9 @@ class MethodUsages/*<-example::MethodUsages#*/ {
m/*->example::MethodUsages#m.*/.m18/*->example::Methods#m18().*/(1)
m/*->example::MethodUsages#m.*/.m18/*->example::Methods#m18(+1).*/("")
}

object MethodUsages/*<-example::MethodUsages.*/ {
var x/*<-example::MethodUsages.x().*/: Int/*->scala::Int#*/ = 1
def x/*<-example::MethodUsages.x(+1).*/(n/*<-example::MethodUsages.x(+1).(n)*/: String/*->scala::Predef.String#*/): Int/*->scala::Int#*/ = /*->scala::Predef.augmentString().*/n/*->example::MethodUsages.x(+1).(n)*/.toInt/*->scala::collection::StringOps#toInt().*/
def foo/*<-example::MethodUsages.foo().*/: Int/*->scala::Int#*/ = x/*->example::MethodUsages.x().*/ +/*->scala::Int#`+`(+4).*/ x/*->example::MethodUsages.x(+1).*/("22")
}
6 changes: 6 additions & 0 deletions tests/semanticdb/expect/MethodUsages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,9 @@ class MethodUsages {
m.m18(1)
m.m18("")
}

object MethodUsages {
var x: Int = 1
def x(n: String): Int = n.toInt
def foo: Int = x + x("22")
}
25 changes: 25 additions & 0 deletions tests/semanticdb/expect/recursion.expect.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// semanticdb traversal of annotations of Nat caused an infinite loop
package recursion

object Nats/*<-recursion::Nats.*/ {
sealed trait Nat/*<-recursion::Nats.Nat#*/ {
inline def ++/*<-recursion::Nats.Nat#`++`().*/ : Succ/*->recursion::Nats.Succ#*/[this.type] = Succ/*->recursion::Nats.Succ.*//*->recursion::Nats.Succ.apply().*/(this)

transparent inline def +/*<-recursion::Nats.Nat#`+`().*/(inline that/*<-recursion::Nats.Nat#`+`().(that)*/: Nat/*->recursion::Nats.Nat#*/): Nat/*->recursion::Nats.Nat#*/ =
inline this match {
case Zero/*->recursion::Nats.Zero.*/ => that/*->recursion::Nats.Nat#`+`().(that)*/
case Succ/*->recursion::Nats.Succ.*//*->recursion::Nats.Succ.unapply().*//*->local0*/(p/*<-local1*/) => p/*->local1*/ +/*->recursion::Nats.Nat#`+`().*/ that/*->recursion::Nats.Nat#`+`().(that)*/.++/*->recursion::Nats.Nat#`++`().*/
}
}

case object Zero/*<-recursion::Nats.Zero.*/ extends Nat/*->recursion::Nats.Nat#*/
case class Succ/*<-recursion::Nats.Succ#*/[N/*<-recursion::Nats.Succ#[N]*/ <: Nat](p/*<-recursion::Nats.Succ#p.*/: N/*->recursion::Nats.Succ#[N]*/) extends Nat/*->recursion::Nats.Nat#*/

transparent inline def toIntg/*<-recursion::Nats.toIntg().*/(inline n/*<-recursion::Nats.toIntg().(n)*/: Nat/*->recursion::Nats.Nat#*/): Int/*->scala::Int#*/ =
inline n/*->recursion::Nats.toIntg().(n)*/ match {
case Zero/*->recursion::Nats.Zero.*/ => 0
case Succ/*->recursion::Nats.Succ.*//*->recursion::Nats.Succ.unapply().*//*->local2*/(p/*<-local3*/) => toIntg/*->recursion::Nats.toIntg().*/(p/*->local3*/) +/*->scala::Int#`+`(+4).*/ 1
}

val j31/*<-recursion::Nats.j31.*/ = toIntg/*->recursion::Nats.toIntg().*/(Zero/*->recursion::Nats.Zero.*/.++.++.++/*<-local4*//*->recursion::Nats.Zero.*//*->recursion::Nats.Nat#`++`().*/ + /*<-local5*//*->recursion::Nats.Nat#`++`().*/Zer/*<-local6*//*->recursion::Nats.Nat#`++`().*//*->recursion::Nats.Nat#`+`().*/o/*->recursion::Nats.Zero.*/.++/*->recursion::Nats.Nat#`++`().*/)
}
25 changes: 25 additions & 0 deletions tests/semanticdb/expect/recursion.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// semanticdb traversal of annotations of Nat caused an infinite loop
package recursion

object Nats {
sealed trait Nat {
inline def ++ : Succ[this.type] = Succ(this)

transparent inline def +(inline that: Nat): Nat =
inline this match {
case Zero => that
case Succ(p) => p + that.++
}
}

case object Zero extends Nat
case class Succ[N <: Nat](p: N) extends Nat

transparent inline def toIntg(inline n: Nat): Int =
inline n match {
case Zero => 0
case Succ(p) => toIntg(p) + 1
}

val j31 = toIntg(Zero.++.++.++ + Zero.++)
}
Loading