Skip to content

Commit

Permalink
Merge pull request #267 from romanowski/sealed-impr
Browse files Browse the repository at this point in the history
Implement used scopes for used names and implement PatMat scope
  • Loading branch information
dwijnand committed Mar 28, 2017
2 parents c75e28d + 9ce4dc3 commit c9d567f
Show file tree
Hide file tree
Showing 53 changed files with 1,051 additions and 331 deletions.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ lazy val zincPersist = (project in internalPath / "zinc-persist").
// Implements the core functionality of detecting and propagating changes incrementally.
// Defines the data structures for representing file fingerprints and relationships and the overall source analysis
lazy val zincCore = (project in internalPath / "zinc-core").
dependsOn(zincApiInfo, zincClasspath, compilerBridge % Test).
dependsOn(zincApiInfo, zincClasspath, compilerInterface, compilerBridge % Test).
configure(addBaseSettingsAndTestDeps).
settings(
// we need to fork because in unit tests we set usejavacp = true which means
Expand Down
34 changes: 2 additions & 32 deletions internal/compiler-bridge/src/main/scala/xsbt/API.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ package xsbt
import scala.tools.nsc.Phase
import scala.tools.nsc.symtab.Flags
import xsbti.api._
import java.util.{ HashMap => JavaMap }

object API {
val name = "xsbt-api"
Expand All @@ -36,34 +35,6 @@ final class API(val global: CallbackGlobal) extends Compat with GlobalHelpers {

private def processUnit(unit: CompilationUnit) = if (!unit.isJava) processScalaUnit(unit)

private def debugOutput(map: JavaMap[String, Array[String]]): String = {
val stringBuffer = new StringBuffer()
// Optimized while loop that uses Java collection
val it = map.entrySet().iterator()
while (it.hasNext) {
val values = it.next()
stringBuffer.append(showUsedNames(values.getKey, values.getValue))
}

stringBuffer.toString
}

private def showUsedNames(className: String, names: Array[String]): String =
s"$className:\n\t${names.mkString(",")}"

private def register(allUsedNames: JavaMap[String, Array[String]]) = {
// Optimized while loop that uses Java collection
val it = allUsedNames.entrySet.iterator()
while (it.hasNext) {
val usedNameInfo = it.next()
val className = usedNameInfo.getKey
val namesIterator = usedNameInfo.getValue.iterator
while (namesIterator.hasNext) {
callback.usedName(className, namesIterator.next())
}
}
}

private def processScalaUnit(unit: CompilationUnit): Unit = {
val sourceFile = unit.source.file.file
debuglog("Traversing " + sourceFile)
Expand All @@ -73,11 +44,10 @@ final class API(val global: CallbackGlobal) extends Compat with GlobalHelpers {
traverser.apply(unit.body)

val extractUsedNames = new ExtractUsedNames[global.type](global)
val allUsedNames = extractUsedNames.extract(unit)
debuglog(s"The $sourceFile contains the following used names:\n ${debugOutput(allUsedNames)}")
register(allUsedNames)
extractUsedNames.extractAndReport(unit)

val classApis = traverser.allNonLocalClasses

classApis.foreach(callback.api(sourceFile, _))
}
}
Expand Down
198 changes: 143 additions & 55 deletions internal/compiler-bridge/src/main/scala/xsbt/ExtractUsedNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ package xsbt

import java.util.{ HashMap => JavaMap }
import java.util.{ HashSet => JavaSet }
import java.util.EnumSet

import xsbti.UseScope

import Compat._

Expand Down Expand Up @@ -49,47 +52,101 @@ import Compat._
*
*/
class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) extends Compat with ClassName with GlobalHelpers {

import global._
import JavaUtils._

private final class NamesUsedInClass {
// Default names and other scopes are separated for performance reasons
val defaultNames: JavaSet[Name] = new JavaSet[global.Name]()
val scopedNames: JavaMap[Name, EnumSet[UseScope]] = new JavaMap[Name, EnumSet[UseScope]]()

def extract(unit: CompilationUnit): JavaMap[String, Array[String]] = {
// We have to leave with commas on ends
override def toString(): String = {
val builder = new StringBuilder(": ")
defaultNames.foreach { name =>
builder.append(name.decoded.trim)
val otherScopes = scopedNames.get(name)
if (otherScopes != null) {
builder.append(" in [")
otherScopes.foreach(scope => builder.append(scope.name()).append(", "))
builder.append("]")
}
builder.append(", ")
}
builder.toString()
}
}

private def DefaultScopes = EnumSet.of(UseScope.Default)
private def PatmatScopes = EnumSet.of(UseScope.PatMatTarget)

def extractAndReport(unit: CompilationUnit): Unit = {
val tree = unit.body
val traverser = new ExtractUsedNamesTraverser
traverser.traverse(tree)

val namesUsedAtTopLevel = traverser.namesUsedAtTopLevel
val defaultNamesTopLevel = namesUsedAtTopLevel.defaultNames
val scopedNamesTopLevel = namesUsedAtTopLevel.scopedNames

if (!namesUsedAtTopLevel.isEmpty) {
// Handle names used at top level that cannot be related to an owner
if (!defaultNamesTopLevel.isEmpty || !scopedNamesTopLevel.isEmpty) {
val responsible = firstClassOrModuleDef(tree)
responsible match {
case Some(classOrModuleDef) =>
val sym = classOrModuleDef.symbol
val firstClassSymbol = if (sym.isModule) sym.moduleClass else sym
val firstClassSymbol = enclOrModuleClass(sym)
val firstClassName = className(firstClassSymbol)
traverser.usedNamesFromClass(firstClassName).addAll(namesUsedAtTopLevel)
val namesInFirstClass = traverser.usedNamesFromClass(firstClassName)
val scopedNamesInFirstClass = namesInFirstClass.scopedNames

namesInFirstClass.defaultNames.addAll(defaultNamesTopLevel)
scopedNamesTopLevel.foreach { (topLevelName, topLevelScopes) =>
val existingScopes = scopedNamesInFirstClass.get(topLevelName)
if (existingScopes == null)
scopedNamesInFirstClass.put(topLevelName, topLevelScopes)
else existingScopes.addAll(topLevelScopes)
()
}

case None =>
reporter.warning(unit.position(0), Feedback.OrphanNames)
}
}

val result = new JavaMap[String, Array[String]]()

val it = traverser.usedNamesFromClasses.entrySet().iterator()
while (it.hasNext) {
val usedNamePair = it.next()
val className = usedNamePair.getKey.toString.trim
val usedNames = usedNamePair.getValue
val usedNamesIt = usedNames.iterator
val convertedUsedNames = new Array[String](usedNames.size)

var i = 0
while (usedNamesIt.hasNext) {
convertedUsedNames(i) = usedNamesIt.next.decode.trim
i += 1
debuglog {
val msg = s"The ${unit.source} contains the following used names:\n"
val builder = new StringBuilder(msg)
traverser.usedNamesFromClasses.foreach {
(name, usedNames) =>
builder
.append(name.toString.trim)
.append(": ")
.append(usedNames.toString())
.append("\n")
()
}

result.put(className, convertedUsedNames)
builder.toString()
}

result
// Handle names circumscribed to classes
traverser.usedNamesFromClasses.foreach {
(rawClassName, usedNames) =>
val className = rawClassName.toString.trim
usedNames.defaultNames.foreach { rawUsedName =>
val useName = rawUsedName.decoded.trim
val existingScopes = usedNames.scopedNames.get(rawUsedName)
val useScopes = {
if (existingScopes == null) DefaultScopes
else {
existingScopes.add(UseScope.Default)
existingScopes
}
}
callback.usedName(className, useName, useScopes)
}
}
}

private def firstClassOrModuleDef(tree: Tree): Option[Tree] = {
Expand All @@ -101,8 +158,9 @@ class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) ext
}

private class ExtractUsedNamesTraverser extends Traverser {
val usedNamesFromClasses = new JavaMap[Name, JavaSet[Name]]()
val namesUsedAtTopLevel = new JavaSet[Name]()

val usedNamesFromClasses = new JavaMap[Name, NamesUsedInClass]()
val namesUsedAtTopLevel = new NamesUsedInClass

override def traverse(tree: Tree): Unit = {
handleClassicTreeNode(tree)
Expand All @@ -115,20 +173,20 @@ class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) ext
if (!ignoredSymbol(symbol)) {
val name = symbol.name
// Synthetic names are no longer included. See https://github.com/sbt/sbt/issues/2537
if (!isEmptyName(name) && !names.contains(name))
if (!isEmptyName(name))
names.add(name)
()
}
}

/** Returns mutable set with all names from given class used in current context */
def usedNamesFromClass(className: Name): JavaSet[Name] = {
val ts = usedNamesFromClasses.get(className)
if (ts == null) {
val emptySet = new JavaSet[Name]()
usedNamesFromClasses.put(className, emptySet)
emptySet
} else ts
def usedNamesFromClass(className: Name): NamesUsedInClass = {
val names = usedNamesFromClasses.get(className)
if (names == null) {
val newOne = new NamesUsedInClass
usedNamesFromClasses.put(className, newOne)
newOne
} else names
}

/*
Expand All @@ -148,6 +206,21 @@ class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) ext
}
}

private object PatMatDependencyTraverser extends TypeDependencyTraverser {
override def addDependency(symbol: global.Symbol): Unit = {
if (!ignoredSymbol(symbol) && symbol.isSealed) {
val name = symbol.name
if (!isEmptyName(name)) {
val existingScopes = _currentScopedNamesCache.get(name)
if (existingScopes == null)
_currentScopedNamesCache.put(name, PatmatScopes)
else existingScopes.add(UseScope.PatMatTarget)
}
}
()
}
}

private object TypeDependencyTraverser extends TypeDependencyTraverser {
private val ownersCache = new JavaMap[Symbol, JavaSet[Type]]()
private var nameCache: JavaSet[Name] = _
Expand Down Expand Up @@ -175,6 +248,10 @@ class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) ext
}

private def handleClassicTreeNode(tree: Tree): Unit = tree match {
// Register names from pattern match target type in PatMatTarget scope
case ValDef(mods, _, tpt, _) if mods.isCase && mods.isSynthetic =>
updateCurrentOwner()
PatMatDependencyTraverser.traverse(tpt.tpe)
case _: DefTree | _: Template => ()
case Import(_, selectors: List[ImportSelector]) =>
val names = getNamesOfEnclosingScope
Expand All @@ -198,6 +275,7 @@ class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) ext
inspectedTypeTrees.add(original)
original.foreach(traverse)
}

case t if t.hasSymbolField =>
val symbol = t.symbol
if (symbol != rootMirror.RootPackage)
Expand All @@ -216,54 +294,64 @@ class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) ext
private var _currentOwner: Symbol = _
private var _currentNonLocalClass: Symbol = _
private var _currentNamesCache: JavaSet[Name] = _
private var _currentScopedNamesCache: JavaMap[Name, EnumSet[UseScope]] = _

@inline private def resolveNonLocal(from: Symbol): Symbol = {
val fromClass = enclOrModuleClass(from)
if (ignoredSymbol(fromClass) || fromClass.hasPackageFlag) NoSymbol
else localToNonLocalClass.resolveNonLocal(fromClass)
}

@inline private def getNames(nonLocalClass: Symbol): JavaSet[Name] = {
@inline private def namesInClass(nonLocalClass: Symbol): NamesUsedInClass = {
if (nonLocalClass == NoSymbol) namesUsedAtTopLevel
else usedNamesFromClass(ExtractUsedNames.this.className(nonLocalClass))
}

/**
* Return the names associated with the closest non-local class owner
* of a tree given `currentOwner`, defined and updated by `Traverser`.
* Updates caches for closest non-local class owner of a tree given
* `currentOwner`, defined and updated by `Traverser`.
*
* This method modifies the state associated with the names variable
* `_currentNamesCache`, which is composed by `_currentOwner` and
* and `_currentNonLocalClass`.
* `_currentNamesCache` and `_currentScopedNamesCache`, which are composed
* by `_currentOwner` and and `_currentNonLocalClass`.
*
* The used caching strategy works as follows:
* 1. Return previous non-local class if owners are referentially equal.
* * The used caching strategy works as follows:
* 1. Do nothing if owners are referentially equal.
* 2. Otherwise, check if they resolve to the same non-local class.
* 1. If they do, overwrite `_isLocalSource` and return
* `_currentNonLocalClass`.
* 1. If they do, do nothing
* 2. Otherwise, overwrite all the pertinent fields to be consistent.
*/
private def getNamesOfEnclosingScope: JavaSet[Name] = {
private def updateCurrentOwner(): Unit = {
if (_currentOwner == null) {
// Set the first state for the enclosing non-local class
_currentOwner = currentOwner
_currentNonLocalClass = resolveNonLocal(currentOwner)
_currentNamesCache = getNames(_currentNonLocalClass)
_currentNamesCache
} else {
if (_currentOwner == currentOwner) _currentNamesCache
else {
val nonLocalClass = resolveNonLocal(currentOwner)
if (_currentNonLocalClass == nonLocalClass) _currentNamesCache
else {
_currentNonLocalClass = nonLocalClass
_currentNamesCache = getNames(nonLocalClass)
_currentOwner = currentOwner
_currentNamesCache
}
val usedInClass = namesInClass(_currentNonLocalClass)
_currentNamesCache = usedInClass.defaultNames
_currentScopedNamesCache = usedInClass.scopedNames
} else if (_currentOwner != currentOwner) {
val nonLocalClass = resolveNonLocal(currentOwner)
if (_currentNonLocalClass != nonLocalClass) {
_currentOwner = currentOwner
_currentNonLocalClass = nonLocalClass
val usedInClass = namesInClass(_currentNonLocalClass)
_currentNamesCache = usedInClass.defaultNames
_currentScopedNamesCache = usedInClass.scopedNames
}

}
}

/**
* Return the names associated with the closest non-local class owner
* of a tree given `currentOwner`, defined and updated by `Traverser`.
*
* This method modifies the state associated with the names variable
* by calling `updateCurrentOwner()`.
*/
@inline
private def getNamesOfEnclosingScope: JavaSet[Name] = {
updateCurrentOwner()
_currentNamesCache
}
}
}
Loading

0 comments on commit c9d567f

Please sign in to comment.