diff --git a/src/main/scala/xsbt/API.scala b/src/main/scala/xsbt/API.scala index c18c33731946..5b6809dfc82e 100644 --- a/src/main/scala/xsbt/API.scala +++ b/src/main/scala/xsbt/API.scala @@ -22,14 +22,13 @@ final class API(val global: CallbackGlobal) extends Compat with GlobalHelpers { class ApiPhase(prev: Phase) extends GlobalPhase(prev) { override def description = "Extracts the public API from source files." def name = API.name - override def run(): Unit = - { - val start = System.currentTimeMillis - super.run() - callback.apiPhaseCompleted() - val stop = System.currentTimeMillis - debuglog("API phase took : " + ((stop - start) / 1000.0) + " s") - } + override def run(): Unit = { + val start = System.currentTimeMillis + super.run() + callback.apiPhaseCompleted() + val stop = System.currentTimeMillis + debuglog("API phase took : " + ((stop - start) / 1000.0) + " s") + } def apply(unit: global.CompilationUnit): Unit = processUnit(unit) @@ -52,7 +51,8 @@ final class API(val global: CallbackGlobal) extends Compat with GlobalHelpers { } } - private final class TopLevelHandler(extractApi: ExtractAPI[global.type]) extends TopLevelTraverser { + private final class TopLevelHandler(extractApi: ExtractAPI[global.type]) + extends TopLevelTraverser { def allNonLocalClasses: Set[ClassLike] = { extractApi.allExtractedNonLocalClasses } @@ -73,11 +73,11 @@ final class API(val global: CallbackGlobal) extends Compat with GlobalHelpers { } def isTopLevel(sym: Symbol): Boolean = { !ignoredSymbol(sym) && - sym.isStatic && - !sym.isImplClass && - !sym.hasFlag(Flags.SYNTHETIC) && - !sym.hasFlag(Flags.JAVA) && - !sym.isNestedClass + sym.isStatic && + !sym.isImplClass && + !sym.hasFlag(Flags.SYNTHETIC) && + !sym.hasFlag(Flags.JAVA) && + !sym.isNestedClass } } diff --git a/src/main/scala/xsbt/Analyzer.scala b/src/main/scala/xsbt/Analyzer.scala index 276a1b68293b..b8d2b4c7607a 100644 --- a/src/main/scala/xsbt/Analyzer.scala +++ b/src/main/scala/xsbt/Analyzer.scala @@ -17,7 +17,8 @@ final class Analyzer(val global: CallbackGlobal) extends LocateClassFile { def newPhase(prev: Phase): Phase = new AnalyzerPhase(prev) private class AnalyzerPhase(prev: Phase) extends GlobalPhase(prev) { - override def description = "Finds concrete instances of provided superclasses, and application entry points." + override def description = + "Finds concrete instances of provided superclasses, and application entry points." def name = Analyzer.name def apply(unit: CompilationUnit): Unit = { if (!unit.isJava) { @@ -38,7 +39,10 @@ final class Analyzer(val global: CallbackGlobal) extends LocateClassFile { if (!isLocalClass) { val srcClassName = classNameAsString(sym) val binaryClassName = flatclassName(sym, '.', separatorRequired) - callback.generatedNonLocalClass(sourceFile, classFile, binaryClassName, srcClassName) + callback.generatedNonLocalClass(sourceFile, + classFile, + binaryClassName, + srcClassName) } else { callback.generatedLocalClass(sourceFile, classFile) } diff --git a/src/main/scala/xsbt/ClassName.scala b/src/main/scala/xsbt/ClassName.scala index 1bc590e12275..1dba29fed1cb 100644 --- a/src/main/scala/xsbt/ClassName.scala +++ b/src/main/scala/xsbt/ClassName.scala @@ -38,14 +38,15 @@ trait ClassName extends Compat { * If `s` represents a package object `pkg3`, then the returned name will be `pkg1.pkg2.pkg3.package`. * If `s` represents a class `Foo` nested in package object `pkg3` then the returned name is `pkg1.pkg2.pk3.Foo`. */ - protected def classNameAsSeenIn(in: Symbol, s: Symbol): String = enteringPhase(currentRun.picklerPhase.next) { - if (in.isRoot || in.isRootPackage || in == NoSymbol || in.isEffectiveRoot) - s.simpleName.toString - else if (in.isPackageObjectOrClass) - in.owner.fullName + "." + s.name - else - in.fullName + "." + s.name - } + protected def classNameAsSeenIn(in: Symbol, s: Symbol): String = + enteringPhase(currentRun.picklerPhase.next) { + if (in.isRoot || in.isRootPackage || in == NoSymbol || in.isEffectiveRoot) + s.simpleName.toString + else if (in.isPackageObjectOrClass) + in.owner.fullName + "." + s.name + else + in.fullName + "." + s.name + } private def pickledName(s: Symbol): Name = enteringPhase(currentRun.picklerPhase.next) { s.fullNameAsName('.') } diff --git a/src/main/scala/xsbt/Command.scala b/src/main/scala/xsbt/Command.scala index 9621c6d317c7..9a97579dc0a1 100644 --- a/src/main/scala/xsbt/Command.scala +++ b/src/main/scala/xsbt/Command.scala @@ -11,6 +11,7 @@ import scala.tools.nsc.{ CompilerCommand, Settings } import Compat._ object Command { + /** * Construct a CompilerCommand using reflection, to be compatible with Scalac before and after * r21274 @@ -21,7 +22,11 @@ object Command { constr(classOf[List[_]], classOf[Settings]).newInstance(arguments, settings) } catch { case _: NoSuchMethodException => - constr(classOf[List[_]], classOf[Settings], classOf[(_) => _], classOf[Boolean]).newInstance(arguments, settings, (s: String) => throw new RuntimeException(s), false.asInstanceOf[AnyRef]) + constr(classOf[List[_]], classOf[Settings], classOf[(_) => _], classOf[Boolean]) + .newInstance(arguments, + settings, + (s: String) => throw new RuntimeException(s), + false.asInstanceOf[AnyRef]) } } diff --git a/src/main/scala/xsbt/CompilerInterface.scala b/src/main/scala/xsbt/CompilerInterface.scala index c63050999dec..7b108a040c39 100644 --- a/src/main/scala/xsbt/CompilerInterface.scala +++ b/src/main/scala/xsbt/CompilerInterface.scala @@ -16,10 +16,20 @@ import Log.debug import java.io.File final class CompilerInterface { - def newCompiler(options: Array[String], output: Output, initialLog: Logger, initialDelegate: Reporter, resident: Boolean): CachedCompiler = + def newCompiler(options: Array[String], + output: Output, + initialLog: Logger, + initialDelegate: Reporter, + resident: Boolean): CachedCompiler = new CachedCompiler0(options, output, new WeakLog(initialLog, initialDelegate), resident) - def run(sources: Array[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, delegate: Reporter, progress: CompileProgress, cached: CachedCompiler): Unit = + def run(sources: Array[File], + changes: DependencyChanges, + callback: AnalysisCallback, + log: Logger, + delegate: Reporter, + progress: CompileProgress, + cached: CachedCompiler): Unit = cached.run(sources, changes, callback, log, delegate, progress) } // for compatibility with Scala versions without Global.registerTopLevelSym (2.8.1 and earlier) @@ -29,7 +39,11 @@ sealed trait GlobalCompat { self: Global => def informUnitStarting(phase: Phase, unit: CompilationUnit): Unit = () } } -sealed abstract class CallbackGlobal(settings: Settings, reporter: reporters.Reporter, output: Output) extends Global(settings, reporter) with GlobalCompat { +sealed abstract class CallbackGlobal(settings: Settings, + reporter: reporters.Reporter, + output: Output) + extends Global(settings, reporter) + with GlobalCompat { def callback: AnalysisCallback def findClass(name: String): Option[(AbstractFile, Boolean)] lazy val outputDirs: Iterable[File] = { @@ -57,9 +71,13 @@ sealed abstract class CallbackGlobal(settings: Settings, reporter: reporters.Rep */ private[xsbt] val localToNonLocalClass = new LocalToNonLocalClass[this.type](this) } -class InterfaceCompileFailed(val arguments: Array[String], val problems: Array[Problem], override val toString: String) extends xsbti.CompileFailed +class InterfaceCompileFailed(val arguments: Array[String], + val problems: Array[Problem], + override val toString: String) + extends xsbti.CompileFailed -class InterfaceCompileCancelled(val arguments: Array[String], override val toString: String) extends xsbti.CompileCancelled +class InterfaceCompileCancelled(val arguments: Array[String], override val toString: String) + extends xsbti.CompileCancelled private final class WeakLog(private[this] var log: Logger, private[this] var delegate: Reporter) { def apply(message: String): Unit = { @@ -74,12 +92,18 @@ private final class WeakLog(private[this] var log: Logger, private[this] var del } } -private final class CachedCompiler0(args: Array[String], output: Output, initialLog: WeakLog, resident: Boolean) extends CachedCompiler with CachedCompilerCompat { +private final class CachedCompiler0(args: Array[String], + output: Output, + initialLog: WeakLog, + resident: Boolean) + extends CachedCompiler + with CachedCompilerCompat { val settings = new Settings(s => initialLog(s)) output match { case multi: MultipleOutput => for (out <- multi.outputGroups) - settings.outputDirs.add(out.sourceDirectory.getAbsolutePath, out.outputDirectory.getAbsolutePath) + settings.outputDirs + .add(out.sourceDirectory.getAbsolutePath, out.outputDirectory.getAbsolutePath) case single: SingleOutput => settings.outputDirs.setSingleOutput(single.outputDirectory.getAbsolutePath) } @@ -91,27 +115,46 @@ private final class CachedCompiler0(args: Array[String], output: Output, initial dreporter.printSummary() handleErrors(dreporter, initialLog.logger) } - } finally - initialLog.clear() + } finally initialLog.clear() def noErrors(dreporter: DelegatingReporter) = !dreporter.hasErrors && command.ok def commandArguments(sources: Array[File]): Array[String] = (command.settings.recreateArgs ++ sources.map(_.getAbsolutePath)).toArray[String] - def run(sources: Array[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, delegate: Reporter, progress: CompileProgress): Unit = synchronized { - debug(log, "Running cached compiler " + hashCode.toLong.toHexString + ", interfacing (CompilerInterface) with Scala compiler " + scala.tools.nsc.Properties.versionString) + def run(sources: Array[File], + changes: DependencyChanges, + callback: AnalysisCallback, + log: Logger, + delegate: Reporter, + progress: CompileProgress): Unit = synchronized { + debug( + log, + "Running cached compiler " + hashCode.toLong.toHexString + ", interfacing (CompilerInterface) with Scala compiler " + scala.tools.nsc.Properties.versionString + ) val dreporter = DelegatingReporter(settings, delegate) - try { run(sources.toList, changes, callback, log, dreporter, progress) } - finally { dreporter.dropDelegate() } + try { run(sources.toList, changes, callback, log, dreporter, progress) } finally { + dreporter.dropDelegate() + } } - private[this] def run(sources: List[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, dreporter: DelegatingReporter, compileProgress: CompileProgress): Unit = { + private[this] def run(sources: List[File], + changes: DependencyChanges, + callback: AnalysisCallback, + log: Logger, + dreporter: DelegatingReporter, + compileProgress: CompileProgress): Unit = { if (command.shouldStopWithInfo) { dreporter.info(null, command.getInfoMessage(compiler), true) - throw new InterfaceCompileFailed(args, Array(), "Compiler option supplied that disabled actual compilation.") + throw new InterfaceCompileFailed( + args, + Array(), + "Compiler option supplied that disabled actual compilation.") } if (noErrors(dreporter)) { - debug(log, args.mkString("Calling Scala compiler with arguments (CompilerInterface):\n\t", "\n\t", "")) + debug(log, + args.mkString("Calling Scala compiler with arguments (CompilerInterface):\n\t", + "\n\t", + "")) compiler.set(callback, dreporter) val run = new compiler.Run with compiler.RunCompat { override def informUnitStarting(phase: Phase, unit: compiler.CompilationUnit): Unit = { @@ -125,7 +168,9 @@ private final class CachedCompiler0(args: Array[String], output: Output, initial val sortedSourceFiles = sources.map(_.getAbsolutePath).sortWith(_ < _) run compile sortedSourceFiles processUnreportedWarnings(run) - dreporter.problems foreach { p => callback.problem(p.category, p.position, p.message, p.severity, true) } + dreporter.problems foreach { p => + callback.problem(p.category, p.position, p.message, p.severity, true) + } } dreporter.printSummary() if (!noErrors(dreporter)) handleErrors(dreporter, log) @@ -134,11 +179,10 @@ private final class CachedCompiler0(args: Array[String], output: Output, initial // all of them (because we cancelled the compilation) if (dreporter.cancelled) handleCompilationCancellation(dreporter, log) } - def handleErrors(dreporter: DelegatingReporter, log: Logger): Nothing = - { - debug(log, "Compilation failed (CompilerInterface)") - throw new InterfaceCompileFailed(args, dreporter.problems, "Compilation failed") - } + def handleErrors(dreporter: DelegatingReporter, log: Logger): Nothing = { + debug(log, "Compilation failed (CompilerInterface)") + throw new InterfaceCompileFailed(args, dreporter.problems, "Compilation failed") + } def handleCompilationCancellation(dreporter: DelegatingReporter, log: Logger): Nothing = { assert(dreporter.cancelled, "We should get here only if when compilation got cancelled") debug(log, "Compilation cancelled (CompilerInterface)") @@ -146,13 +190,14 @@ private final class CachedCompiler0(args: Array[String], output: Output, initial } def processUnreportedWarnings(run: compiler.Run): Unit = { // allConditionalWarnings and the ConditionalWarning class are only in 2.10+ - final class CondWarnCompat(val what: String, val warnings: mutable.ListBuffer[(compiler.Position, String)]) + final class CondWarnCompat(val what: String, + val warnings: mutable.ListBuffer[(compiler.Position, String)]) implicit def compat(run: AnyRef): Compat = new Compat final class Compat { def allConditionalWarnings = List[CondWarnCompat]() } val warnings = run.allConditionalWarnings if (warnings.nonEmpty) - compiler.logUnreportedWarnings(warnings.map(cw => ("" /*cw.what*/ , cw.warnings.toList))) + compiler.logUnreportedWarnings(warnings.map(cw => ("" /*cw.what*/, cw.warnings.toList))) } val compiler: Compiler = newCompiler @@ -207,28 +252,27 @@ private final class CachedCompiler0(args: Array[String], output: Output, initial def name = phaseName } - override lazy val phaseDescriptors = - { - phasesSet += sbtAnalyzer - if (callback.enabled()) { - phasesSet += sbtDependency - phasesSet += apiExtractor - } - superComputePhaseDescriptors + override lazy val phaseDescriptors = { + phasesSet += sbtAnalyzer + if (callback.enabled()) { + phasesSet += sbtDependency + phasesSet += apiExtractor } + superComputePhaseDescriptors + } private[this] def superComputePhaseDescriptors() = this.computePhaseDescriptors private[this] def superDropRun(): Unit = try { superCall("dropRun"); () } catch { case e: NoSuchMethodException => () } // dropRun not in 2.8.1 - private[this] def superCall(methodName: String): AnyRef = - { - val meth = classOf[Global].getDeclaredMethod(methodName) - meth.setAccessible(true) - meth.invoke(this) - } + private[this] def superCall(methodName: String): AnyRef = { + val meth = classOf[Global].getDeclaredMethod(methodName) + meth.setAccessible(true) + meth.invoke(this) + } def logUnreportedWarnings(seq: Seq[(String, List[(Position, String)])]): Unit = // Scala 2.10.x and later { val drep = reporter.asInstanceOf[DelegatingReporter] - for ((what, warnings) <- seq; (pos, msg) <- warnings) yield callback.problem(what, drep.convert(pos), msg, Severity.Warn, false) + for ((what, warnings) <- seq; (pos, msg) <- warnings) + yield callback.problem(what, drep.convert(pos), msg, Severity.Warn, false) () } @@ -245,12 +289,11 @@ private final class CachedCompiler0(args: Array[String], output: Output, initial def findClass(name: String): Option[(AbstractFile, Boolean)] = getOutputClass(name).map(f => (f, true)) orElse findOnClassPath(name).map(f => (f, false)) - def getOutputClass(name: String): Option[AbstractFile] = - { - // This could be improved if a hint where to look is given. - val className = name.replace('.', '/') + ".class" - outputDirs map (new File(_, className)) find (_.exists) map (AbstractFile.getFile(_)) - } + def getOutputClass(name: String): Option[AbstractFile] = { + // This could be improved if a hint where to look is given. + val className = name.replace('.', '/') + ".class" + outputDirs map (new File(_, className)) find (_.exists) map (AbstractFile.getFile(_)) + } def findOnClassPath(name: String): Option[AbstractFile] = classPath.findClass(name).flatMap(_.binary.asInstanceOf[Option[AbstractFile]]) diff --git a/src/main/scala/xsbt/ConsoleFactory.scala b/src/main/scala/xsbt/ConsoleFactory.scala index faa885b03955..602fe50e6a77 100644 --- a/src/main/scala/xsbt/ConsoleFactory.scala +++ b/src/main/scala/xsbt/ConsoleFactory.scala @@ -10,10 +10,22 @@ package xsbt import xsbti.Logger class ConsoleFactory extends xsbti.ConsoleFactory { - def createConsole(args: Array[String], bootClasspathString: String, - classpathString: String, initialCommands: String, cleanupCommands: String, - loader: ClassLoader, bindNames: Array[String], bindValues: Array[AnyRef], - log: Logger): xsbti.ConsoleInterface = - new ConsoleInterface(args, bootClasspathString, classpathString, - initialCommands, cleanupCommands, loader, bindNames, bindValues, log) + def createConsole(args: Array[String], + bootClasspathString: String, + classpathString: String, + initialCommands: String, + cleanupCommands: String, + loader: ClassLoader, + bindNames: Array[String], + bindValues: Array[AnyRef], + log: Logger): xsbti.ConsoleInterface = + new ConsoleInterface(args, + bootClasspathString, + classpathString, + initialCommands, + cleanupCommands, + loader, + bindNames, + bindValues, + log) } diff --git a/src/main/scala/xsbt/ConsoleInterface.scala b/src/main/scala/xsbt/ConsoleInterface.scala index 06155171e972..a361ea524a74 100644 --- a/src/main/scala/xsbt/ConsoleInterface.scala +++ b/src/main/scala/xsbt/ConsoleInterface.scala @@ -15,14 +15,25 @@ import ConsoleHelper._ import scala.tools.nsc.interpreter.IMain import scala.tools.nsc.{ GenericRunnerCommand, Settings } -class ConsoleInterface(args: Array[String], bootClasspathString: String, - classpathString: String, initialCommands: String, cleanupCommands: String, - loader: ClassLoader, bindNames: Array[String], bindValues: Array[AnyRef], - log: Logger) extends xsbti.ConsoleInterface { - lazy val interpreterSettings = MakeSettings.sync(args.toList, { message => log.error(Message(message)) }) +class ConsoleInterface(args: Array[String], + bootClasspathString: String, + classpathString: String, + initialCommands: String, + cleanupCommands: String, + loader: ClassLoader, + bindNames: Array[String], + bindValues: Array[AnyRef], + log: Logger) + extends xsbti.ConsoleInterface { + lazy val interpreterSettings = MakeSettings.sync(args.toList, { message => + log.error(Message(message)) + }) // we need rt.jar from JDK, so java classpath is required val useJavaCp = "-usejavacp" - val compilerSettings = MakeSettings.sync(args :+ useJavaCp, bootClasspathString, classpathString, { message => log.error(Message(message)) }) + val compilerSettings = + MakeSettings.sync(args :+ useJavaCp, bootClasspathString, classpathString, { message => + log.error(Message(message)) + }) if (!bootClasspathString.isEmpty) compilerSettings.bootclasspath.value = bootClasspathString compilerSettings.classpath.value = classpathString @@ -33,12 +44,11 @@ class ConsoleInterface(args: Array[String], bootClasspathString: String, def lastReq = prevRequestList.last } - override def interpret(line: String, synthetic: Boolean): ConsoleResponse = - { - clearBuffer() - val r = interpreter.interpret(line, synthetic) - ConsoleResponse(r, outWriter.toString) - } + override def interpret(line: String, synthetic: Boolean): ConsoleResponse = { + clearBuffer() + val r = interpreter.interpret(line, synthetic) + ConsoleResponse(r, outWriter.toString) + } def clearBuffer(): Unit = { // errorWriter.getBuffer.setLength(0) outWriter.getBuffer.setLength(0) @@ -51,22 +61,23 @@ class ConsoleInterface(args: Array[String], bootClasspathString: String, } object MakeSettings { - def apply(args: List[String], onError: String => Unit) = - { - val command = new GenericRunnerCommand(args, onError(_)) - if (command.ok) command.settings - // TODO: Provide better exception - else throw new Exception(command.usageMsg) - } + def apply(args: List[String], onError: String => Unit) = { + val command = new GenericRunnerCommand(args, onError(_)) + if (command.ok) command.settings + // TODO: Provide better exception + else throw new Exception(command.usageMsg) + } - def sync(args: Array[String], bootClasspathString: String, classpathString: String, onError: String => Unit): Settings = - { - val compilerSettings = sync(args.toList, onError) - if (!bootClasspathString.isEmpty) - compilerSettings.bootclasspath.value = bootClasspathString - compilerSettings.classpath.value = classpathString - compilerSettings - } + def sync(args: Array[String], + bootClasspathString: String, + classpathString: String, + onError: String => Unit): Settings = { + val compilerSettings = sync(args.toList, onError) + if (!bootClasspathString.isEmpty) + compilerSettings.bootclasspath.value = bootClasspathString + compilerSettings.classpath.value = classpathString + compilerSettings + } def sync(options: List[String], onError: String => Unit) = { val settings = apply(options, onError) diff --git a/src/main/scala/xsbt/DelegatingReporter.scala b/src/main/scala/xsbt/DelegatingReporter.scala index b9306193f3b2..fd745e313a73 100644 --- a/src/main/scala/xsbt/DelegatingReporter.scala +++ b/src/main/scala/xsbt/DelegatingReporter.scala @@ -15,8 +15,14 @@ private object DelegatingReporter { def apply(settings: scala.tools.nsc.Settings, delegate: xsbti.Reporter): DelegatingReporter = new DelegatingReporter(Command.getWarnFatal(settings), Command.getNoWarn(settings), delegate) - class PositionImpl(sourcePath0: Option[String], sourceFile0: Option[File], - line0: Option[Int], lineContent0: String, offset0: Option[Int], pointer0: Option[Int], pointerSpace0: Option[String]) extends xsbti.Position { + class PositionImpl(sourcePath0: Option[String], + sourceFile0: Option[File], + line0: Option[Int], + lineContent0: String, + offset0: Option[Int], + pointer0: Option[Int], + pointerSpace0: Option[String]) + extends xsbti.Position { val line = o2oi(line0) val lineContent = lineContent0 val offset = o2oi(offset0) @@ -48,7 +54,10 @@ private object DelegatingReporter { // The following code is based on scala.tools.nsc.reporters.{AbstractReporter, ConsoleReporter} // Copyright 2002-2009 LAMP/EPFL // Original author: Martin Odersky -private final class DelegatingReporter(warnFatal: Boolean, noWarn: Boolean, private[this] var delegate: xsbti.Reporter) extends scala.tools.nsc.reporters.Reporter { +private final class DelegatingReporter(warnFatal: Boolean, + noWarn: Boolean, + private[this] var delegate: xsbti.Reporter) + extends scala.tools.nsc.reporters.Reporter { import scala.reflect.internal.util.{ FakePos, NoPosition, Position } import DelegatingReporter._ def dropDelegate(): Unit = { delegate = null } @@ -72,31 +81,36 @@ private final class DelegatingReporter(warnFatal: Boolean, noWarn: Boolean, priv delegate.log(convert(pos), msg, convert(severity)) } } - def convert(posIn: Position): xsbti.Position = - { - val posOpt = - Option(posIn) match { - case None | Some(NoPosition) => None - case Some(_: FakePos) => None - case _ => Option(posIn.finalPosition) - } - posOpt match { - case None => new PositionImpl(None, None, None, "", None, None, None) - case Some(pos) => makePosition(pos) + def convert(posIn: Position): xsbti.Position = { + val posOpt = + Option(posIn) match { + case None | Some(NoPosition) => None + case Some(_: FakePos) => None + case _ => Option(posIn.finalPosition) } + posOpt match { + case None => new PositionImpl(None, None, None, "", None, None, None) + case Some(pos) => makePosition(pos) } - private[this] def makePosition(pos: Position): xsbti.Position = - { - val src = pos.source - val sourcePath = src.file.path - val sourceFile = src.file.file - val line = pos.line - val lineContent = pos.lineContent.stripLineEnd - val offset = pos.point - val pointer = offset - src.lineToOffset(src.offsetToLine(offset)) - val pointerSpace = (lineContent: Seq[Char]).take(pointer).map { case '\t' => '\t'; case x => ' ' }.mkString - new PositionImpl(Option(sourcePath), Option(sourceFile), Option(line), lineContent, Option(offset), Option(pointer), Option(pointerSpace)) - } + } + private[this] def makePosition(pos: Position): xsbti.Position = { + val src = pos.source + val sourcePath = src.file.path + val sourceFile = src.file.file + val line = pos.line + val lineContent = pos.lineContent.stripLineEnd + val offset = pos.point + val pointer = offset - src.lineToOffset(src.offsetToLine(offset)) + val pointerSpace = + (lineContent: Seq[Char]).take(pointer).map { case '\t' => '\t'; case x => ' ' }.mkString + new PositionImpl(Option(sourcePath), + Option(sourceFile), + Option(line), + lineContent, + Option(offset), + Option(pointer), + Option(pointerSpace)) + } import xsbti.Severity.{ Info, Warn, Error } private[this] def convert(sev: Severity): xsbti.Severity = diff --git a/src/main/scala/xsbt/Dependency.scala b/src/main/scala/xsbt/Dependency.scala index 85a8604d9a83..20ce91c9d6ed 100644 --- a/src/main/scala/xsbt/Dependency.scala +++ b/src/main/scala/xsbt/Dependency.scala @@ -218,17 +218,17 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile with * 3. Inheritance. */ private def addClassDependency( - cache: JavaSet[ClassDependency], - process: ClassDependency => Unit, - fromClass: Symbol, - dep: Symbol + cache: JavaSet[ClassDependency], + process: ClassDependency => Unit, + fromClass: Symbol, + dep: Symbol ): Unit = { assert(fromClass.isClass, Feedback.expectedClassSymbol(fromClass)) val depClass = enclOrModuleClass(dep) val dependency = ClassDependency(fromClass, depClass) if (!cache.contains(dependency) && - fromClass.associatedFile != depClass.associatedFile && - !depClass.isRefinementClass) { + fromClass.associatedFile != depClass.associatedFile && + !depClass.isRefinementClass) { process(dependency) cache.add(dependency) () @@ -354,17 +354,21 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile with case Template(parents, self, body) => // use typeSymbol to dealias type aliases -- we want to track the dependency on the real class in the alias's RHS - def flattenTypeToSymbols(tp: Type): List[Symbol] = if (tp eq null) Nil - else tp match { - // rt.typeSymbol is redundant if we list out all parents, TODO: what about rt.decls? - case rt: RefinedType => rt.parents.flatMap(flattenTypeToSymbols) - case _ => List(tp.typeSymbol) - } + def flattenTypeToSymbols(tp: Type): List[Symbol] = + if (tp eq null) Nil + else + tp match { + // rt.typeSymbol is redundant if we list out all parents, TODO: what about rt.decls? + case rt: RefinedType => rt.parents.flatMap(flattenTypeToSymbols) + case _ => List(tp.typeSymbol) + } val inheritanceTypes = parents.map(_.tpe).toSet val inheritanceSymbols = inheritanceTypes.flatMap(flattenTypeToSymbols) - debuglog("Parent types for " + tree.symbol + " (self: " + self.tpt.tpe + "): " + inheritanceTypes + " with symbols " + inheritanceSymbols.map(_.fullName)) + debuglog( + "Parent types for " + tree.symbol + " (self: " + self.tpt.tpe + "): " + inheritanceTypes + " with symbols " + inheritanceSymbols + .map(_.fullName)) inheritanceSymbols.foreach { symbol => addInheritanceDependency(symbol) diff --git a/src/main/scala/xsbt/ExtractAPI.scala b/src/main/scala/xsbt/ExtractAPI.scala index f0f3657c9526..0f9eb42abfae 100644 --- a/src/main/scala/xsbt/ExtractAPI.scala +++ b/src/main/scala/xsbt/ExtractAPI.scala @@ -47,11 +47,13 @@ import scala.tools.nsc.Global * */ class ExtractAPI[GlobalType <: Global]( - val global: GlobalType, - // Tracks the source file associated with the CompilationUnit currently being processed by the API phase. - // This is used when recording inheritance dependencies. - sourceFile: File -) extends Compat with ClassName with GlobalHelpers { + val global: GlobalType, + // Tracks the source file associated with the CompilationUnit currently being processed by the API phase. + // This is used when recording inheritance dependencies. + sourceFile: File +) extends Compat + with ClassName + with GlobalHelpers { import global._ @@ -168,28 +170,29 @@ class ExtractAPI[GlobalType <: Global]( } private def thisPath(sym: Symbol) = path(pathComponents(sym, Constants.thisPath :: Nil)) - private def path(components: List[PathComponent]) = new xsbti.api.Path(components.toArray[PathComponent]) - private def pathComponents(sym: Symbol, postfix: List[PathComponent]): List[PathComponent] = - { - if (sym == NoSymbol || sym.isRoot || sym.isEmptyPackageClass || sym.isRootPackage) postfix - else pathComponents(sym.owner, new xsbti.api.Id(simpleName(sym)) :: postfix) - } - private def types(in: Symbol, t: List[Type]): Array[xsbti.api.Type] = t.toArray[Type].map(processType(in, _)) - private def projectionType(in: Symbol, pre: Type, sym: Symbol) = - { - if (pre == NoPrefix) { - if (sym.isLocalClass || sym.isRoot || sym.isRootPackage) Constants.emptyType - else if (sym.isTypeParameterOrSkolem || sym.isExistentiallyBound) reference(sym) - else { - // this appears to come from an existential type in an inherited member- not sure why isExistential is false here - /*println("Warning: Unknown prefixless type: " + sym + " in " + sym.owner + " in " + sym.enclClass) + private def path(components: List[PathComponent]) = + new xsbti.api.Path(components.toArray[PathComponent]) + private def pathComponents(sym: Symbol, postfix: List[PathComponent]): List[PathComponent] = { + if (sym == NoSymbol || sym.isRoot || sym.isEmptyPackageClass || sym.isRootPackage) postfix + else pathComponents(sym.owner, new xsbti.api.Id(simpleName(sym)) :: postfix) + } + private def types(in: Symbol, t: List[Type]): Array[xsbti.api.Type] = + t.toArray[Type].map(processType(in, _)) + private def projectionType(in: Symbol, pre: Type, sym: Symbol) = { + if (pre == NoPrefix) { + if (sym.isLocalClass || sym.isRoot || sym.isRootPackage) Constants.emptyType + else if (sym.isTypeParameterOrSkolem || sym.isExistentiallyBound) reference(sym) + else { + // this appears to come from an existential type in an inherited member- not sure why isExistential is false here + /*println("Warning: Unknown prefixless type: " + sym + " in " + sym.owner + " in " + sym.enclClass) println("\tFlags: " + sym.flags + ", istype: " + sym.isType + ", absT: " + sym.isAbstractType + ", alias: " + sym.isAliasType + ", nonclass: " + isNonClassType(sym))*/ - reference(sym) - } - } else if (sym.isRoot || sym.isRootPackage) Constants.emptyType - else new xsbti.api.Projection(processType(in, pre), simpleName(sym)) - } - private def reference(sym: Symbol): xsbti.api.ParameterRef = new xsbti.api.ParameterRef(tparamID(sym)) + reference(sym) + } + } else if (sym.isRoot || sym.isRootPackage) Constants.emptyType + else new xsbti.api.Projection(processType(in, pre), simpleName(sym)) + } + private def reference(sym: Symbol): xsbti.api.ParameterRef = + new xsbti.api.ParameterRef(tparamID(sym)) // The compiler only pickles static annotations, so only include these in the API. // This way, the API is not sensitive to whether we compiled from source or loaded from classfile. @@ -198,8 +201,14 @@ class ExtractAPI[GlobalType <: Global]( staticAnnotations(as).toArray.map { a => new xsbti.api.Annotation( processType(in, a.atp), - if (a.assocs.isEmpty) Array(new xsbti.api.AnnotationArgument("", a.args.mkString("(", ",", ")"))) // what else to do with a Tree? - else a.assocs.map { case (name, value) => new xsbti.api.AnnotationArgument(name.toString, value.toString) }.toArray[xsbti.api.AnnotationArgument] + if (a.assocs.isEmpty) + Array(new xsbti.api.AnnotationArgument("", a.args.mkString("(", ",", ")"))) // what else to do with a Tree? + else + a.assocs + .map { + case (name, value) => new xsbti.api.AnnotationArgument(name.toString, value.toString) + } + .toArray[xsbti.api.AnnotationArgument] ) } @@ -210,64 +219,77 @@ class ExtractAPI[GlobalType <: Global]( // annotations from bean methods are not handled because: // a) they are recorded as normal source methods anyway // b) there is no way to distinguish them from user-defined methods - val associated = List(b, b.getterIn(b.enclClass), b.setterIn(b.enclClass)).filter(_ != NoSymbol) + val associated = + List(b, b.getterIn(b.enclClass), b.setterIn(b.enclClass)).filter(_ != NoSymbol) associated.flatMap(ss => mkAnnotations(in, ss.annotations)).distinct.toArray } private def viewer(s: Symbol) = (if (s.isModule) s.moduleClass else s).thisType - private def defDef(in: Symbol, s: Symbol): xsbti.api.Def = - { - def build(t: Type, typeParams: Array[xsbti.api.TypeParameter], valueParameters: List[xsbti.api.ParameterList]): xsbti.api.Def = - { - def parameterList(syms: List[Symbol]): xsbti.api.ParameterList = - { - val isImplicitList = syms match { case head :: _ => isImplicit(head); case _ => false } - new xsbti.api.ParameterList(syms.map(parameterS).toArray, isImplicitList) - } - t match { - case PolyType(typeParams0, base) => - assert(typeParams.isEmpty) - assert(valueParameters.isEmpty) - build(base, typeParameters(in, typeParams0), Nil) - case MethodType(params, resultType) => - build(resultType, typeParams, parameterList(params) :: valueParameters) - case NullaryMethodType(resultType) => - build(resultType, typeParams, valueParameters) - case returnType => - val retType = processType(in, dropConst(returnType)) - new xsbti.api.Def(simpleName(s), getAccess(s), getModifiers(s), annotations(in, s), - typeParams, valueParameters.reverse.toArray, retType) - } - } - def parameterS(s: Symbol): xsbti.api.MethodParameter = { - val tp: global.Type = s.info - makeParameter(simpleName(s), tp, tp.typeSymbol, s) + private def defDef(in: Symbol, s: Symbol): xsbti.api.Def = { + def build(t: Type, + typeParams: Array[xsbti.api.TypeParameter], + valueParameters: List[xsbti.api.ParameterList]): xsbti.api.Def = { + def parameterList(syms: List[Symbol]): xsbti.api.ParameterList = { + val isImplicitList = syms match { case head :: _ => isImplicit(head); case _ => false } + new xsbti.api.ParameterList(syms.map(parameterS).toArray, isImplicitList) + } + t match { + case PolyType(typeParams0, base) => + assert(typeParams.isEmpty) + assert(valueParameters.isEmpty) + build(base, typeParameters(in, typeParams0), Nil) + case MethodType(params, resultType) => + build(resultType, typeParams, parameterList(params) :: valueParameters) + case NullaryMethodType(resultType) => + build(resultType, typeParams, valueParameters) + case returnType => + val retType = processType(in, dropConst(returnType)) + new xsbti.api.Def(simpleName(s), + getAccess(s), + getModifiers(s), + annotations(in, s), + typeParams, + valueParameters.reverse.toArray, + retType) } + } + def parameterS(s: Symbol): xsbti.api.MethodParameter = { + val tp: global.Type = s.info + makeParameter(simpleName(s), tp, tp.typeSymbol, s) + } - // paramSym is only for 2.8 and is to determine if the parameter has a default - def makeParameter(name: String, tpe: Type, ts: Symbol, paramSym: Symbol): xsbti.api.MethodParameter = - { - import xsbti.api.ParameterModifier._ - val (t, special) = - if (ts == definitions.RepeatedParamClass) // || s == definitions.JavaRepeatedParamClass) - (tpe.typeArgs.head, Repeated) - else if (ts == definitions.ByNameParamClass) - (tpe.typeArgs.head, ByName) - else - (tpe, Plain) - new xsbti.api.MethodParameter(name, processType(in, t), hasDefault(paramSym), special) - } - val t = viewer(in).memberInfo(s) - build(t, Array(), Nil) + // paramSym is only for 2.8 and is to determine if the parameter has a default + def makeParameter(name: String, + tpe: Type, + ts: Symbol, + paramSym: Symbol): xsbti.api.MethodParameter = { + import xsbti.api.ParameterModifier._ + val (t, special) = + if (ts == definitions.RepeatedParamClass) // || s == definitions.JavaRepeatedParamClass) + (tpe.typeArgs.head, Repeated) + else if (ts == definitions.ByNameParamClass) + (tpe.typeArgs.head, ByName) + else + (tpe, Plain) + new xsbti.api.MethodParameter(name, processType(in, t), hasDefault(paramSym), special) } + val t = viewer(in).memberInfo(s) + build(t, Array(), Nil) + } private def hasDefault(s: Symbol) = s != NoSymbol && s.hasFlag(Flags.DEFAULTPARAM) - private def fieldDef[T](in: Symbol, s: Symbol, keepConst: Boolean, create: (String, xsbti.api.Access, xsbti.api.Modifiers, Array[xsbti.api.Annotation], xsbti.api.Type) => T): T = - { - val t = dropNullary(viewer(in).memberType(s)) - val t2 = if (keepConst) t else dropConst(t) - create(simpleName(s), getAccess(s), getModifiers(s), annotations(in, s), processType(in, t2)) - } + private def fieldDef[T](in: Symbol, + s: Symbol, + keepConst: Boolean, + create: (String, + xsbti.api.Access, + xsbti.api.Modifiers, + Array[xsbti.api.Annotation], + xsbti.api.Type) => T): T = { + val t = dropNullary(viewer(in).memberType(s)) + val t2 = if (keepConst) t else dropConst(t) + create(simpleName(s), getAccess(s), getModifiers(s), annotations(in, s), processType(in, t2)) + } private def dropConst(t: Type): Type = t match { case ConstantType(constant) => constant.tpe case _ => t @@ -277,29 +299,36 @@ class ExtractAPI[GlobalType <: Global]( case _ => t } - private def typeDef(in: Symbol, s: Symbol): xsbti.api.TypeMember = - { - val (typeParams, tpe) = - viewer(in).memberInfo(s) match { - case PolyType(typeParams0, base) => (typeParameters(in, typeParams0), base) - case t => (Array[xsbti.api.TypeParameter](), t) - } - val name = simpleName(s) - val access = getAccess(s) - val modifiers = getModifiers(s) - val as = annotations(in, s) - - if (s.isAliasType) - new xsbti.api.TypeAlias(name, access, modifiers, as, typeParams, processType(in, tpe)) - else if (s.isAbstractType) { - val bounds = tpe.bounds - new xsbti.api.TypeDeclaration(name, access, modifiers, as, typeParams, processType(in, bounds.lo), processType(in, bounds.hi)) - } else - error("Unknown type member" + s) - } + private def typeDef(in: Symbol, s: Symbol): xsbti.api.TypeMember = { + val (typeParams, tpe) = + viewer(in).memberInfo(s) match { + case PolyType(typeParams0, base) => (typeParameters(in, typeParams0), base) + case t => (Array[xsbti.api.TypeParameter](), t) + } + val name = simpleName(s) + val access = getAccess(s) + val modifiers = getModifiers(s) + val as = annotations(in, s) + + if (s.isAliasType) + new xsbti.api.TypeAlias(name, access, modifiers, as, typeParams, processType(in, tpe)) + else if (s.isAbstractType) { + val bounds = tpe.bounds + new xsbti.api.TypeDeclaration(name, + access, + modifiers, + as, + typeParams, + processType(in, bounds.lo), + processType(in, bounds.hi)) + } else + error("Unknown type member" + s) + } - private def structure(info: Type, s: Symbol): xsbti.api.Structure = structureCache.getOrElseUpdate(s, mkStructure(info, s)) - private def structureWithInherited(info: Type, s: Symbol): xsbti.api.Structure = structureCache.getOrElseUpdate(s, mkStructureWithInherited(info, s)) + private def structure(info: Type, s: Symbol): xsbti.api.Structure = + structureCache.getOrElseUpdate(s, mkStructure(info, s)) + private def structureWithInherited(info: Type, s: Symbol): xsbti.api.Structure = + structureCache.getOrElseUpdate(s, mkStructureWithInherited(info, s)) private def removeConstructors(ds: List[Symbol]): List[Symbol] = ds filter { !_.isConstructor } @@ -340,67 +369,78 @@ class ExtractAPI[GlobalType <: Global]( // but that does not take linearization into account. def linearizedAncestorTypes(info: Type): List[Type] = info.baseClasses.tail.map(info.baseType) - private def mkStructure(s: Symbol, bases: List[Type], declared: List[Symbol], inherited: List[Symbol]): xsbti.api.Structure = { - new xsbti.api.Structure(lzy(types(s, bases)), lzy(processDefinitions(s, declared)), lzy(processDefinitions(s, inherited))) + private def mkStructure(s: Symbol, + bases: List[Type], + declared: List[Symbol], + inherited: List[Symbol]): xsbti.api.Structure = { + new xsbti.api.Structure(lzy(types(s, bases)), + lzy(processDefinitions(s, declared)), + lzy(processDefinitions(s, inherited))) } - private def processDefinitions(in: Symbol, defs: List[Symbol]): Array[xsbti.api.ClassDefinition] = + private def processDefinitions(in: Symbol, + defs: List[Symbol]): Array[xsbti.api.ClassDefinition] = sort(defs.toArray).flatMap((d: Symbol) => definition(in, d)) private[this] def sort(defs: Array[Symbol]): Array[Symbol] = { Arrays.sort(defs, sortClasses) defs } - private def definition(in: Symbol, sym: Symbol): Option[xsbti.api.ClassDefinition] = - { - def mkVar = Some(fieldDef(in, sym, keepConst = false, new xsbti.api.Var(_, _, _, _, _))) - def mkVal = Some(fieldDef(in, sym, keepConst = true, new xsbti.api.Val(_, _, _, _, _))) - if (isClass(sym)) - if (ignoreClass(sym)) None else Some(classLike(in, sym)) - else if (sym.isNonClassType) - Some(typeDef(in, sym)) - else if (sym.isVariable) - if (isSourceField(sym)) mkVar else None - else if (sym.isStable) - if (isSourceField(sym)) mkVal else None - else if (sym.isSourceMethod && !sym.isSetter) - if (sym.isGetter) mkVar else Some(defDef(in, sym)) - else - None - } + private def definition(in: Symbol, sym: Symbol): Option[xsbti.api.ClassDefinition] = { + def mkVar = Some(fieldDef(in, sym, keepConst = false, new xsbti.api.Var(_, _, _, _, _))) + def mkVal = Some(fieldDef(in, sym, keepConst = true, new xsbti.api.Val(_, _, _, _, _))) + if (isClass(sym)) + if (ignoreClass(sym)) None else Some(classLike(in, sym)) + else if (sym.isNonClassType) + Some(typeDef(in, sym)) + else if (sym.isVariable) + if (isSourceField(sym)) mkVar else None + else if (sym.isStable) + if (isSourceField(sym)) mkVal else None + else if (sym.isSourceMethod && !sym.isSetter) + if (sym.isGetter) mkVar else Some(defDef(in, sym)) + else + None + } private def ignoreClass(sym: Symbol): Boolean = sym.isLocalClass || sym.isAnonymousClass || sym.fullName.endsWith(tpnme.LOCAL_CHILD.toString) // This filters private[this] vals/vars that were not in the original source. // The getter will be used for processing instead. - private def isSourceField(sym: Symbol): Boolean = - { - val getter = sym.getterIn(sym.enclClass) - // the check `getter eq sym` is a precaution against infinite recursion - // `isParamAccessor` does not exist in all supported versions of Scala, so the flag check is done directly - (getter == NoSymbol && !sym.hasFlag(Flags.PARAMACCESSOR)) || (getter eq sym) - } - private def getModifiers(s: Symbol): xsbti.api.Modifiers = - { - import Flags._ - val absOver = s.hasFlag(ABSOVERRIDE) - val abs = s.hasFlag(ABSTRACT) || s.hasFlag(DEFERRED) || absOver - val over = s.hasFlag(OVERRIDE) || absOver - new xsbti.api.Modifiers(abs, over, s.isFinal, s.hasFlag(SEALED), isImplicit(s), s.hasFlag(LAZY), s.hasFlag(MACRO), s.hasFlag(SUPERACCESSOR)) - } + private def isSourceField(sym: Symbol): Boolean = { + val getter = sym.getterIn(sym.enclClass) + // the check `getter eq sym` is a precaution against infinite recursion + // `isParamAccessor` does not exist in all supported versions of Scala, so the flag check is done directly + (getter == NoSymbol && !sym.hasFlag(Flags.PARAMACCESSOR)) || (getter eq sym) + } + private def getModifiers(s: Symbol): xsbti.api.Modifiers = { + import Flags._ + val absOver = s.hasFlag(ABSOVERRIDE) + val abs = s.hasFlag(ABSTRACT) || s.hasFlag(DEFERRED) || absOver + val over = s.hasFlag(OVERRIDE) || absOver + new xsbti.api.Modifiers(abs, + over, + s.isFinal, + s.hasFlag(SEALED), + isImplicit(s), + s.hasFlag(LAZY), + s.hasFlag(MACRO), + s.hasFlag(SUPERACCESSOR)) + } private def isImplicit(s: Symbol) = s.hasFlag(Flags.IMPLICIT) - private def getAccess(c: Symbol): xsbti.api.Access = - { - if (c.isPublic) Constants.public - else if (c.isPrivateLocal) Constants.privateLocal - else if (c.isProtectedLocal) Constants.protectedLocal - else { - val within = c.privateWithin - val qualifier = if (within == NoSymbol) Constants.unqualified else new xsbti.api.IdQualifier(within.fullName) - if (c.hasFlag(Flags.PROTECTED)) new xsbti.api.Protected(qualifier) - else new xsbti.api.Private(qualifier) - } + private def getAccess(c: Symbol): xsbti.api.Access = { + if (c.isPublic) Constants.public + else if (c.isPrivateLocal) Constants.privateLocal + else if (c.isProtectedLocal) Constants.protectedLocal + else { + val within = c.privateWithin + val qualifier = + if (within == NoSymbol) Constants.unqualified + else new xsbti.api.IdQualifier(within.fullName) + if (c.hasFlag(Flags.PROTECTED)) new xsbti.api.Protected(qualifier) + else new xsbti.api.Private(qualifier) } + } /** * Replace all types that directly refer to the `forbidden` symbol by `NoType`. @@ -412,74 +452,83 @@ class ExtractAPI[GlobalType <: Global]( else mapOver(tp) } - private def processType(in: Symbol, t: Type): xsbti.api.Type = typeCache.getOrElseUpdate((in, t), makeType(in, t)) - private def makeType(in: Symbol, t: Type): xsbti.api.Type = - { + private def processType(in: Symbol, t: Type): xsbti.api.Type = + typeCache.getOrElseUpdate((in, t), makeType(in, t)) + private def makeType(in: Symbol, t: Type): xsbti.api.Type = { - val dealiased = t match { - case TypeRef(_, sym, _) if sym.isAliasType => t.dealias - case _ => t - } + val dealiased = t match { + case TypeRef(_, sym, _) if sym.isAliasType => t.dealias + case _ => t + } - dealiased match { - case NoPrefix => Constants.emptyType - case ThisType(sym) => new xsbti.api.Singleton(thisPath(sym)) - case SingleType(pre, sym) => projectionType(in, pre, sym) - case ConstantType(constant) => new xsbti.api.Constant(processType(in, constant.tpe), constant.stringValue) - - /* explaining the special-casing of references to refinement classes (https://support.typesafe.com/tickets/1882) - * - * goal: a representation of type references to refinement classes that's stable across compilation runs - * (and thus insensitive to typing from source or unpickling from bytecode) - * - * problem: the current representation, which corresponds to the owner chain of the refinement: - * 1. is affected by pickling, so typing from source or using unpickled symbols give different results (because the unpickler "localizes" owners -- this could be fixed in the compiler) - * 2. can't distinguish multiple refinements in the same owner (this is a limitation of SBT's internal representation and cannot be fixed in the compiler) - * - * potential solutions: - * - simply drop the reference: won't work as collapsing all refinement types will cause recompilation to be skipped when a refinement is changed to another refinement - * - represent the symbol in the api: can't think of a stable way of referring to an anonymous symbol whose owner changes when pickled - * + expand the reference to the corresponding refinement type: doing that recursively may not terminate, but we can deal with that by approximating recursive references - * (all we care about is being sound for recompilation: recompile iff a dependency changes, and this will happen as long as we have one unrolling of the reference to the refinement) - */ - case TypeRef(pre, sym, Nil) if sym.isRefinementClass => - // Since we only care about detecting changes reliably, we unroll a reference to a refinement class once. - // Recursive references are simply replaced by NoType -- changes to the type will be seen in the first unrolling. - // The API need not be type correct, so this truncation is acceptable. Most of all, the API should be compact. - val unrolling = pre.memberInfo(sym) // this is a refinement type - - // in case there are recursive references, suppress them -- does this ever happen? - // we don't have a test case for this, so warn and hope we'll get a contribution for it :-) - val withoutRecursiveRefs = new SuppressSymbolRef(sym).mapOver(unrolling) - if (unrolling ne withoutRecursiveRefs) - reporter.warning(sym.pos, "sbt-api: approximated refinement ref" + t + " (== " + unrolling + ") to " + withoutRecursiveRefs + "\nThis is currently untested, please report the code you were compiling.") - - structure(withoutRecursiveRefs, sym) - case tr @ TypeRef(pre, sym, args) => - val base = projectionType(in, pre, sym) - if (args.isEmpty) - if (isRawType(tr)) - processType(in, rawToExistential(tr)) - else - base + dealiased match { + case NoPrefix => Constants.emptyType + case ThisType(sym) => new xsbti.api.Singleton(thisPath(sym)) + case SingleType(pre, sym) => projectionType(in, pre, sym) + case ConstantType(constant) => + new xsbti.api.Constant(processType(in, constant.tpe), constant.stringValue) + + /* explaining the special-casing of references to refinement classes (https://support.typesafe.com/tickets/1882) + * + * goal: a representation of type references to refinement classes that's stable across compilation runs + * (and thus insensitive to typing from source or unpickling from bytecode) + * + * problem: the current representation, which corresponds to the owner chain of the refinement: + * 1. is affected by pickling, so typing from source or using unpickled symbols give different results (because the unpickler "localizes" owners -- this could be fixed in the compiler) + * 2. can't distinguish multiple refinements in the same owner (this is a limitation of SBT's internal representation and cannot be fixed in the compiler) + * + * potential solutions: + * - simply drop the reference: won't work as collapsing all refinement types will cause recompilation to be skipped when a refinement is changed to another refinement + * - represent the symbol in the api: can't think of a stable way of referring to an anonymous symbol whose owner changes when pickled + * + expand the reference to the corresponding refinement type: doing that recursively may not terminate, but we can deal with that by approximating recursive references + * (all we care about is being sound for recompilation: recompile iff a dependency changes, and this will happen as long as we have one unrolling of the reference to the refinement) + */ + case TypeRef(pre, sym, Nil) if sym.isRefinementClass => + // Since we only care about detecting changes reliably, we unroll a reference to a refinement class once. + // Recursive references are simply replaced by NoType -- changes to the type will be seen in the first unrolling. + // The API need not be type correct, so this truncation is acceptable. Most of all, the API should be compact. + val unrolling = pre.memberInfo(sym) // this is a refinement type + + // in case there are recursive references, suppress them -- does this ever happen? + // we don't have a test case for this, so warn and hope we'll get a contribution for it :-) + val withoutRecursiveRefs = new SuppressSymbolRef(sym).mapOver(unrolling) + if (unrolling ne withoutRecursiveRefs) + reporter.warning( + sym.pos, + "sbt-api: approximated refinement ref" + t + " (== " + unrolling + ") to " + withoutRecursiveRefs + "\nThis is currently untested, please report the code you were compiling." + ) + + structure(withoutRecursiveRefs, sym) + case tr @ TypeRef(pre, sym, args) => + val base = projectionType(in, pre, sym) + if (args.isEmpty) + if (isRawType(tr)) + processType(in, rawToExistential(tr)) else - new xsbti.api.Parameterized(base, types(in, args)) - case SuperType(thistpe: Type, supertpe: Type) => - warning("sbt-api: Super type (not implemented): this=" + thistpe + ", super=" + supertpe); Constants.emptyType - case at: AnnotatedType => - at.annotations match { - case Nil => processType(in, at.underlying) - case annots => new xsbti.api.Annotated(processType(in, at.underlying), mkAnnotations(in, annots)) - } - case rt: CompoundType => structure(rt, rt.typeSymbol) - case t: ExistentialType => makeExistentialType(in, t) - case NoType => Constants.emptyType // this can happen when there is an error that will be reported by a later phase - case PolyType(typeParams, resultType) => new xsbti.api.Polymorphic(processType(in, resultType), typeParameters(in, typeParams)) - case NullaryMethodType(_) => - warning("sbt-api: Unexpected nullary method type " + in + " in " + in.owner); Constants.emptyType - case _ => warning("sbt-api: Unhandled type " + t.getClass + " : " + t); Constants.emptyType - } + base + else + new xsbti.api.Parameterized(base, types(in, args)) + case SuperType(thistpe: Type, supertpe: Type) => + warning("sbt-api: Super type (not implemented): this=" + thistpe + ", super=" + supertpe); + Constants.emptyType + case at: AnnotatedType => + at.annotations match { + case Nil => processType(in, at.underlying) + case annots => + new xsbti.api.Annotated(processType(in, at.underlying), mkAnnotations(in, annots)) + } + case rt: CompoundType => structure(rt, rt.typeSymbol) + case t: ExistentialType => makeExistentialType(in, t) + case NoType => + Constants.emptyType // this can happen when there is an error that will be reported by a later phase + case PolyType(typeParams, resultType) => + new xsbti.api.Polymorphic(processType(in, resultType), typeParameters(in, typeParams)) + case NullaryMethodType(_) => + warning("sbt-api: Unexpected nullary method type " + in + " in " + in.owner); + Constants.emptyType + case _ => warning("sbt-api: Unhandled type " + t.getClass + " : " + t); Constants.emptyType } + } private def makeExistentialType(in: Symbol, t: ExistentialType): xsbti.api.Existential = { val ExistentialType(typeVariables, qualified) = t existentialRenamings.enterExistentialTypeVariables(typeVariables) @@ -491,20 +540,34 @@ class ExtractAPI[GlobalType <: Global]( existentialRenamings.leaveExistentialTypeVariables(typeVariables) } } - private def typeParameters(in: Symbol, s: Symbol): Array[xsbti.api.TypeParameter] = typeParameters(in, s.typeParams) - private def typeParameters(in: Symbol, s: List[Symbol]): Array[xsbti.api.TypeParameter] = s.map(typeParameter(in, _)).toArray[xsbti.api.TypeParameter] - private def typeParameter(in: Symbol, s: Symbol): xsbti.api.TypeParameter = - { - val varianceInt = s.variance - import xsbti.api.Variance._ - val annots = annotations(in, s) - val variance = if (varianceInt < 0) Contravariant else if (varianceInt > 0) Covariant else Invariant - viewer(in).memberInfo(s) match { - case TypeBounds(low, high) => new xsbti.api.TypeParameter(tparamID(s), annots, typeParameters(in, s), variance, processType(in, low), processType(in, high)) - case PolyType(typeParams, base) => new xsbti.api.TypeParameter(tparamID(s), annots, typeParameters(in, typeParams), variance, processType(in, base.bounds.lo), processType(in, base.bounds.hi)) - case x => error("Unknown type parameter info: " + x.getClass) - } + private def typeParameters(in: Symbol, s: Symbol): Array[xsbti.api.TypeParameter] = + typeParameters(in, s.typeParams) + private def typeParameters(in: Symbol, s: List[Symbol]): Array[xsbti.api.TypeParameter] = + s.map(typeParameter(in, _)).toArray[xsbti.api.TypeParameter] + private def typeParameter(in: Symbol, s: Symbol): xsbti.api.TypeParameter = { + val varianceInt = s.variance + import xsbti.api.Variance._ + val annots = annotations(in, s) + val variance = + if (varianceInt < 0) Contravariant else if (varianceInt > 0) Covariant else Invariant + viewer(in).memberInfo(s) match { + case TypeBounds(low, high) => + new xsbti.api.TypeParameter(tparamID(s), + annots, + typeParameters(in, s), + variance, + processType(in, low), + processType(in, high)) + case PolyType(typeParams, base) => + new xsbti.api.TypeParameter(tparamID(s), + annots, + typeParameters(in, typeParams), + variance, + processType(in, base.bounds.lo), + processType(in, base.bounds.hi)) + case x => error("Unknown type parameter info: " + x.getClass) } + } private def tparamID(s: Symbol): String = existentialRenamings.renaming(s) match { case Some(rename) => @@ -524,7 +587,8 @@ class ExtractAPI[GlobalType <: Global]( // as that invariant is established on completing the class symbol (`mkClassLike` calls `s.initialize` before calling us). // Technically, we could even ignore a self type that's a supertype of the class's type, // as it does not contribute any information relevant outside of the class definition. - if ((s.thisSym eq s) || (s.thisSym.tpeHK == s.tpeHK)) Constants.emptyType else processType(in, s.typeOfThis) + if ((s.thisSym eq s) || (s.thisSym.tpeHK == s.tpeHK)) Constants.emptyType + else processType(in, s.typeOfThis) def extractAllClassesOf(in: Symbol, c: Symbol): Unit = { classLike(in, c) @@ -536,7 +600,8 @@ class ExtractAPI[GlobalType <: Global]( allNonLocalClassesInSrc.toSet } - private def classLike(in: Symbol, c: Symbol): ClassLikeDef = classLikeCache.getOrElseUpdate((in, c), mkClassLike(in, c)) + private def classLike(in: Symbol, c: Symbol): ClassLikeDef = + classLikeCache.getOrElseUpdate((in, c), mkClassLike(in, c)) private def mkClassLike(in: Symbol, c: Symbol): ClassLikeDef = { // Normalize to a class symbol, and initialize it. // (An object -- aka module -- also has a term symbol, @@ -557,9 +622,18 @@ class ExtractAPI[GlobalType <: Global]( val tParams = typeParameters(in, sym) // look at class symbol val selfType = lzy(this.selfType(in, sym)) def constructClass(structure: xsbti.api.Lazy[Structure]): ClassLike = { - new xsbti.api.ClassLike(name, acc, modifiers, anns, - defType, selfType, structure, emptyStringArray, - childrenOfSealedClass, topLevel, tParams) // use original symbol (which is a term symbol when `c.isModule`) for `name` and other non-classy stuff + new xsbti.api.ClassLike( + name, + acc, + modifiers, + anns, + defType, + selfType, + structure, + emptyStringArray, + childrenOfSealedClass, + topLevel, + tParams) // use original symbol (which is a term symbol when `c.isModule`) for `name` and other non-classy stuff } val info = viewer(in).memberInfo(sym) val structure = lzy(structureWithInherited(info, sym)) @@ -568,7 +642,12 @@ class ExtractAPI[GlobalType <: Global]( allNonLocalClassesInSrc += classWithMembers val classDef = new xsbti.api.ClassLikeDef( - name, acc, modifiers, anns, tParams, defType + name, + acc, + modifiers, + anns, + tParams, + defType ) // use original symbol (which is a term symbol when `c.isModule`) for `name` and other non-classy stuff classDef } @@ -610,16 +689,18 @@ class ExtractAPI[GlobalType <: Global]( val emptyType = new xsbti.api.EmptyType } - private def simpleName(s: Symbol): String = - { - val n = s.unexpandedName - val n2 = if (n.toString == "") n else n.decode - n2.toString.trim - } + private def simpleName(s: Symbol): String = { + val n = s.unexpandedName + val n2 = if (n.toString == "") n else n.decode + n2.toString.trim + } private def staticAnnotations(annotations: List[AnnotationInfo]): List[AnnotationInfo] = { // compat stub for 2.8/2.9 - class IsStatic(ann: AnnotationInfo) { def isStatic: Boolean = ann.atp.typeSymbol isNonBottomSubClass definitions.StaticAnnotationClass } + class IsStatic(ann: AnnotationInfo) { + def isStatic: Boolean = + ann.atp.typeSymbol isNonBottomSubClass definitions.StaticAnnotationClass + } implicit def compat(ann: AnnotationInfo): IsStatic = new IsStatic(ann) annotations.filter(_.isStatic) } diff --git a/src/main/scala/xsbt/ExtractUsedNames.scala b/src/main/scala/xsbt/ExtractUsedNames.scala index 497db239465a..bab888d7b5ef 100644 --- a/src/main/scala/xsbt/ExtractUsedNames.scala +++ b/src/main/scala/xsbt/ExtractUsedNames.scala @@ -51,7 +51,10 @@ import Compat._ * The tree walking algorithm walks into TypeTree.original explicitly. * */ -class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) extends Compat with ClassName with GlobalHelpers { +class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) + extends Compat + with ClassName + with GlobalHelpers { import global._ import JavaUtils._ @@ -118,34 +121,32 @@ class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) ext 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") - () + traverser.usedNamesFromClasses.foreach { (name, usedNames) => + builder + .append(name.toString.trim) + .append(": ") + .append(usedNames.toString()) + .append("\n") + () } builder.toString() } // 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 - } + 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) } + callback.usedName(className, useName, useScopes) + } } } @@ -168,15 +169,14 @@ class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) ext super.traverse(tree) } - val addSymbol: (JavaSet[Name], Symbol) => Unit = { - (names: JavaSet[Name], symbol: Symbol) => - 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.add(name) - () - } + val addSymbol: (JavaSet[Name], Symbol) => Unit = { (names: JavaSet[Name], symbol: Symbol) => + 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.add(name) + () + } } /** Returns mutable set with all names from given class used in current context */ diff --git a/src/main/scala/xsbt/GlobalHelpers.scala b/src/main/scala/xsbt/GlobalHelpers.scala index 29112682f2d8..f5afae77716a 100644 --- a/src/main/scala/xsbt/GlobalHelpers.scala +++ b/src/main/scala/xsbt/GlobalHelpers.scala @@ -17,22 +17,22 @@ trait GlobalHelpers { self: Compat => /** Return true if type shall be ignored, false otherwise. */ @inline def ignoredType(tpe: Type) = { tpe == null || - tpe == NoType || - tpe.typeSymbol == EmptyPackageClass + tpe == NoType || + tpe.typeSymbol == EmptyPackageClass } /** Return true if symbol shall be ignored, false otherwise. */ @inline def ignoredSymbol(symbol: Symbol) = { symbol == null || - symbol == NoSymbol || - symbol == EmptyPackageClass + symbol == NoSymbol || + symbol == EmptyPackageClass } /** Return true if name is empty, false otherwise. */ def isEmptyName(name: Name): Boolean = { name match { - case null | nme.EMPTY | nme.EMPTY_PACKAGE_NAME | - tpnme.EMPTY | tpnme.EMPTY_PACKAGE_NAME => true + case null | nme.EMPTY | nme.EMPTY_PACKAGE_NAME | tpnme.EMPTY | tpnme.EMPTY_PACKAGE_NAME => + true case _ => false } } diff --git a/src/main/scala/xsbt/LocateClassFile.scala b/src/main/scala/xsbt/LocateClassFile.scala index a0bfda0c5e9c..aae1a70cf1ea 100644 --- a/src/main/scala/xsbt/LocateClassFile.scala +++ b/src/main/scala/xsbt/LocateClassFile.scala @@ -23,7 +23,8 @@ abstract class LocateClassFile extends Compat with ClassName { protected def classFile(sym: Symbol): Option[(AbstractFile, String)] = // package can never have a corresponding class file; this test does not // catch package objects (that do not have this flag set) - if (sym hasFlag scala.tools.nsc.symtab.Flags.PACKAGE) None else { + if (sym hasFlag scala.tools.nsc.symtab.Flags.PACKAGE) None + else { val file = sym.associatedFile if (file == NoAbstractFile) { diff --git a/src/main/scala/xsbt/ScaladocInterface.scala b/src/main/scala/xsbt/ScaladocInterface.scala index 7ab74be39c3b..c99a6af89e69 100644 --- a/src/main/scala/xsbt/ScaladocInterface.scala +++ b/src/main/scala/xsbt/ScaladocInterface.scala @@ -11,7 +11,8 @@ import xsbti.Logger import Log.debug class ScaladocInterface { - def run(args: Array[String], log: Logger, delegate: xsbti.Reporter) = (new Runner(args, log, delegate)).run + def run(args: Array[String], log: Logger, delegate: xsbti.Reporter) = + (new Runner(args, log, delegate)).run } private class Runner(args: Array[String], log: Logger, delegate: xsbti.Reporter) { import scala.tools.nsc.{ doc, Global, reporters } @@ -29,7 +30,8 @@ private class Runner(args: Array[String], log: Logger, delegate: xsbti.Reporter) processor.document(command.files) } reporter.printSummary() - if (!noErrors) throw new InterfaceCompileFailed(args, reporter.problems, "Scaladoc generation failed") + if (!noErrors) + throw new InterfaceCompileFailed(args, reporter.problems, "Scaladoc generation failed") } object forScope { @@ -56,13 +58,12 @@ private class Runner(args: Array[String], log: Logger, delegate: xsbti.Reporter) val run = new Run run compile command.files - val generator = - { - new DefaultDocDriver { - lazy val global: compiler.type = compiler - lazy val settings = docSettings - } + val generator = { + new DefaultDocDriver { + lazy val global: compiler.type = compiler + lazy val settings = docSettings } + } generator.process(run.units) } } diff --git a/src/main/scala_2.10/xsbt/Compat.scala b/src/main/scala_2.10/xsbt/Compat.scala index a07e6ac6ff37..ce2e09a2c797 100644 --- a/src/main/scala_2.10/xsbt/Compat.scala +++ b/src/main/scala_2.10/xsbt/Compat.scala @@ -82,7 +82,8 @@ abstract class Compat { // Not present in 2.10 @inline final def getterIn(base: Symbol): Symbol = sym.getter(base) - @inline final def setterIn(base: Symbol, hasExpandedName: Boolean = needsExpandedSetterName): Symbol = + @inline final def setterIn(base: Symbol, + hasExpandedName: Boolean = needsExpandedSetterName): Symbol = sym.setter(base, hasExpandedName) // copied from 2.12.1 sources @@ -96,11 +97,10 @@ abstract class Compat { } val DummyValue = 0 - def hasMacro(s: Symbol): Boolean = - { - val MACRO = Flags.MACRO // will be DummyValue for versions before 2.10 - MACRO != DummyValue && s.hasFlag(MACRO.toLong) - } + def hasMacro(s: Symbol): Boolean = { + val MACRO = Flags.MACRO // will be DummyValue for versions before 2.10 + MACRO != DummyValue && s.hasFlag(MACRO.toLong) + } def moduleSuffix(s: Symbol): String = s.moduleSuffix // Not present in 2.10 @@ -109,7 +109,8 @@ abstract class Compat { // Not present in 2.10 @inline final def enteringPhase[T](ph: sri.Phase)(op: => T): T = atPhase[T](ph)(op) - private[this] def sourceCompatibilityOnly: Nothing = throw new RuntimeException("For source compatibility only: should not get here.") + private[this] def sourceCompatibilityOnly: Nothing = + throw new RuntimeException("For source compatibility only: should not get here.") private[this] final implicit def miscCompat(n: AnyRef): MiscCompat = new MiscCompat diff --git a/src/test/scala/xsbt/ClassNameSpecification.scala b/src/test/scala/xsbt/ClassNameSpecification.scala index 6070cabe597e..a207b3171b63 100644 --- a/src/test/scala/xsbt/ClassNameSpecification.scala +++ b/src/test/scala/xsbt/ClassNameSpecification.scala @@ -37,8 +37,13 @@ class ClassNameSpecification extends UnitSpec { val compilerForTesting = new ScalaCompilerForUnitTesting val binaryClassNames = compilerForTesting.extractBinaryClassNamesFromSrc(src) - assert(binaryClassNames === Set("A" -> "A$", "A" -> "A", "A.C" -> "A$C$", "A.C.D" -> "A$C$D$", - "B" -> "B", "B.E" -> "B$E$")) + assert( + binaryClassNames === Set("A" -> "A$", + "A" -> "A", + "A.C" -> "A$C$", + "A.C.D" -> "A$C$D$", + "B" -> "B", + "B.E" -> "B$E$")) } it should "create a binary name for a trait" in { diff --git a/src/test/scala/xsbt/DependencySpecification.scala b/src/test/scala/xsbt/DependencySpecification.scala index 8378baf6f97d..4e256e099420 100644 --- a/src/test/scala/xsbt/DependencySpecification.scala +++ b/src/test/scala/xsbt/DependencySpecification.scala @@ -77,7 +77,8 @@ class DependencySpecification extends UnitSpec { } it should "extract class dependencies from a refinement" in { - val srcFoo = "object Outer {\n class Inner { type Xyz }\n\n type TypeInner = Inner { type Xyz = Int }\n}" + val srcFoo = + "object Outer {\n class Inner { type Xyz }\n\n type TypeInner = Inner { type Xyz = Int }\n}" val srcBar = "object Bar {\n def bar: Outer.TypeInner = null\n}" val compilerForTesting = new ScalaCompilerForUnitTesting @@ -135,7 +136,9 @@ class DependencySpecification extends UnitSpec { val srcH = "class H { import abc.A }" val compilerForTesting = new ScalaCompilerForUnitTesting - val deps = compilerForTesting.extractDependenciesFromSrcs(srcA, srcB, srcC, srcD, srcE, srcF, srcG, srcH).memberRef + val deps = compilerForTesting + .extractDependenciesFromSrcs(srcA, srcB, srcC, srcD, srcE, srcF, srcG, srcH) + .memberRef assert(deps("A") === Set.empty) assert(deps("B") === Set("abc.A", "abc.A.Inner")) @@ -163,8 +166,14 @@ class DependencySpecification extends UnitSpec { val srcH = "trait H extends G.T[Int] with (E[Int] @unchecked)" val compilerForTesting = new ScalaCompilerForUnitTesting - val classDependencies = compilerForTesting.extractDependenciesFromSrcs(srcA, srcB, srcC, srcD, srcE, srcF, srcG, - srcH) + val classDependencies = compilerForTesting.extractDependenciesFromSrcs(srcA, + srcB, + srcC, + srcD, + srcE, + srcF, + srcG, + srcH) classDependencies } diff --git a/src/test/scala/xsbt/ExtractAPISpecification.scala b/src/test/scala/xsbt/ExtractAPISpecification.scala index ddb16b345fa5..99f18172b6d9 100644 --- a/src/test/scala/xsbt/ExtractAPISpecification.scala +++ b/src/test/scala/xsbt/ExtractAPISpecification.scala @@ -139,7 +139,9 @@ class ExtractAPISpecification extends UnitSpec { |} |""".stripMargin val compilerForTesting = new ScalaCompilerForUnitTesting - val apis = compilerForTesting.extractApisFromSrcs(reuseCompilerInstance = false)(List(src1, src2), List(src2)) + val apis = + compilerForTesting.extractApisFromSrcs(reuseCompilerInstance = false)(List(src1, src2), + List(src2)) val _ :: src2Api1 :: src2Api2 :: Nil = apis.toList val namerApi1 = selectNamer(src2Api1) val namerApi2 = selectNamer(src2Api2) @@ -189,9 +191,11 @@ class ExtractAPISpecification extends UnitSpec { val srcC7 = "class C7 { _ => }" val srcC8 = "class C8 { self => }" val compilerForTesting = new ScalaCompilerForUnitTesting - val apis = compilerForTesting.extractApisFromSrcs(reuseCompilerInstance = true)( - List(srcX, srcY, srcC1, srcC2, srcC3, srcC4, srcC5, srcC6, srcC7, srcC8) - ).map(_.head) + val apis = compilerForTesting + .extractApisFromSrcs(reuseCompilerInstance = true)( + List(srcX, srcY, srcC1, srcC2, srcC3, srcC4, srcC5, srcC6, srcC7, srcC8) + ) + .map(_.head) val emptyType = new EmptyType def hasSelfType(c: ClassLike): Boolean = c.selfType != emptyType diff --git a/src/test/scala/xsbt/ExtractUsedNamesPerformanceSpecification.scala b/src/test/scala/xsbt/ExtractUsedNamesPerformanceSpecification.scala index 9e2497215daf..b646e32337a6 100644 --- a/src/test/scala/xsbt/ExtractUsedNamesPerformanceSpecification.scala +++ b/src/test/scala/xsbt/ExtractUsedNamesPerformanceSpecification.scala @@ -11,8 +11,7 @@ import sbt.internal.util.UnitSpec class ExtractUsedNamesPerformanceSpecification extends UnitSpec { private def initFileSystem(uri: URI): Option[FileSystem] = { - try - Option(FileSystems.getFileSystem(uri)) + try Option(FileSystems.getFileSystem(uri)) catch { case _: FileSystemNotFoundException => val env = Map("create" -> "true") @@ -33,24 +32,308 @@ class ExtractUsedNamesPerformanceSpecification extends UnitSpec { val fileUri = getClass.getResource(TestResource).toURI zipfs = initFileSystem(fileUri) new String(Files.readAllBytes(Paths.get(fileUri))) - } finally - zipfs.foreach { fs => try fs.close() catch { case _: Throwable => /*ignore*/ } } + } finally zipfs.foreach { fs => + try fs.close() + catch { case _: Throwable => /*ignore*/ } + } import org.scalatest.concurrent.Timeouts._ import org.scalatest.time.SpanSugar._ val usedNames = failAfter(30 seconds) { val compilerForTesting = new ScalaCompilerForUnitTesting compilerForTesting.extractUsedNamesFromSrc(src) } - val expectedNamesForTupler = Set("", "Object", "scala", "tupler", "TuplerInstances", "DepFn1", "HNil", "$anon", "Out", "Out0", "Tupler", "hnilTupler", "acme", "L", "Aux", "HList", "Serializable", "Unit") - val expectedNamesForTuplerInstances = Set("E", "Tuple4", "e", "case7", "Tuple15", "s", "case19", "T7", "x", "TuplerInstances", "matchEnd19", "T20", "Tuple11", "HNil", "matchEnd6", "p16", "$anon", "T19", "p20", "T2", "p10", "case22", "p19", "n", "Tuple12", "case11", "Tuple22", "p12", "matchEnd7", "N", "p4", "T13", "case26", "Tuple19", "p7", "p5", "j", "Out", "T", "p23", "case15", "matchEnd20", "t", "p21", "matchEnd15", "J", "head", "case13", "u", "matchEnd18", "U", "Tupler", "f", "T8", "T16", "F", "Tuple3", "case8", "case18", "case24", "Boolean", "matchEnd21", "A", "matchEnd26", "a", "Tuple14", "T1", "::", "Nothing", "p18", "case20", "m", "matchEnd10", "M", "matchEnd25", "tail", "Tuple2", "matchEnd5", "p15", "matchEnd23", "I", "i", "matchEnd14", "AnyRef", "Tuple8", "matchEnd8", "case25", "T12", "p3", "case14", "case23", "T5", "matchEnd22", "T17", "v", "p22", "Tuple18", "G", "Tuple13", "matchEnd12", "", "V", "q", "p11", "Q", "case12", "L", "b", "apply", "Object", "g", "B", "l", "==", "Out0", "Tuple1", "matchEnd9", "P", "p2", "T15", "Aux", "matchEnd24", "p", "scala", "matchEnd11", "Tuple20", "HList", "case17", "T9", "p14", "Tuple7", "matchEnd17", "T4", "case28", "T22", "p17", "C", "Tuple6", "MatchError", "T11", "x1", "H", "case16", "matchEnd13", "c", "Tuple9", "h", "T6", "T18", "r", "K", "Tuple17", "p9", "R", "ne", "T14", "case21", "k", "case10", "Tuple21", "O", "case9", "Tuple10", "Any", "T10", "case27", "Tuple5", "D", "p13", "o", "p6", "p8", "matchEnd16", "S", "T21", "Tuple16", "d", "T3") + val expectedNamesForTupler = Set( + "", + "Object", + "scala", + "tupler", + "TuplerInstances", + "DepFn1", + "HNil", + "$anon", + "Out", + "Out0", + "Tupler", + "hnilTupler", + "acme", + "L", + "Aux", + "HList", + "Serializable", + "Unit" + ) + val expectedNamesForTuplerInstances = Set( + "E", + "Tuple4", + "e", + "case7", + "Tuple15", + "s", + "case19", + "T7", + "x", + "TuplerInstances", + "matchEnd19", + "T20", + "Tuple11", + "HNil", + "matchEnd6", + "p16", + "$anon", + "T19", + "p20", + "T2", + "p10", + "case22", + "p19", + "n", + "Tuple12", + "case11", + "Tuple22", + "p12", + "matchEnd7", + "N", + "p4", + "T13", + "case26", + "Tuple19", + "p7", + "p5", + "j", + "Out", + "T", + "p23", + "case15", + "matchEnd20", + "t", + "p21", + "matchEnd15", + "J", + "head", + "case13", + "u", + "matchEnd18", + "U", + "Tupler", + "f", + "T8", + "T16", + "F", + "Tuple3", + "case8", + "case18", + "case24", + "Boolean", + "matchEnd21", + "A", + "matchEnd26", + "a", + "Tuple14", + "T1", + "::", + "Nothing", + "p18", + "case20", + "m", + "matchEnd10", + "M", + "matchEnd25", + "tail", + "Tuple2", + "matchEnd5", + "p15", + "matchEnd23", + "I", + "i", + "matchEnd14", + "AnyRef", + "Tuple8", + "matchEnd8", + "case25", + "T12", + "p3", + "case14", + "case23", + "T5", + "matchEnd22", + "T17", + "v", + "p22", + "Tuple18", + "G", + "Tuple13", + "matchEnd12", + "", + "V", + "q", + "p11", + "Q", + "case12", + "L", + "b", + "apply", + "Object", + "g", + "B", + "l", + "==", + "Out0", + "Tuple1", + "matchEnd9", + "P", + "p2", + "T15", + "Aux", + "matchEnd24", + "p", + "scala", + "matchEnd11", + "Tuple20", + "HList", + "case17", + "T9", + "p14", + "Tuple7", + "matchEnd17", + "T4", + "case28", + "T22", + "p17", + "C", + "Tuple6", + "MatchError", + "T11", + "x1", + "H", + "case16", + "matchEnd13", + "c", + "Tuple9", + "h", + "T6", + "T18", + "r", + "K", + "Tuple17", + "p9", + "R", + "ne", + "T14", + "case21", + "k", + "case10", + "Tuple21", + "O", + "case9", + "Tuple10", + "Any", + "T10", + "case27", + "Tuple5", + "D", + "p13", + "o", + "p6", + "p8", + "matchEnd16", + "S", + "T21", + "Tuple16", + "d", + "T3" + ) val expectedNamesForRefinement = Set("Out0") - val `expectedNamesFor::` = Set("x", "T2", "ScalaRunTime", "Iterator", "T", "head", "asInstanceOf", "Boolean", "A", "$" + "isInstanceOf", "T1", "||", "::", "Nothing", "x$1", "any2stringadd", "acme", "typedProductIterator", "tail", "Tuple2", "AnyRef", "isInstanceOf", "Int", "", "_hashCode", "apply", "Object", "x$0", "==", "Some", "IndexOutOfBoundsException", "T0", "Predef", "scala", "matchEnd4", "HList", "None", "x1", "toString", "H", "+", "&&", "Serializable", "Product", "case6", "::$1", "eq", "Any", "runtime", "String") + val `expectedNamesFor::` = Set( + "x", + "T2", + "ScalaRunTime", + "Iterator", + "T", + "head", + "asInstanceOf", + "Boolean", + "A", + "$" + "isInstanceOf", + "T1", + "||", + "::", + "Nothing", + "x$1", + "any2stringadd", + "acme", + "typedProductIterator", + "tail", + "Tuple2", + "AnyRef", + "isInstanceOf", + "Int", + "", + "_hashCode", + "apply", + "Object", + "x$0", + "==", + "Some", + "IndexOutOfBoundsException", + "T0", + "Predef", + "scala", + "matchEnd4", + "HList", + "None", + "x1", + "toString", + "H", + "+", + "&&", + "Serializable", + "Product", + "case6", + "::$1", + "eq", + "Any", + "runtime", + "String" + ) val expectedNamesForDepFn1 = Set("DepFn1", "Out", "T", "AnyRef", "Object", "scala") - val expectedNamesForHNil = Set("x", "HNil", "ScalaRunTime", "Iterator", "Boolean", "A", "T", "$" + "isInstanceOf", "::", "Nothing", "x$1", "acme", "typedProductIterator", "Int", "", "apply", "Object", "IndexOutOfBoundsException", "scala", "HList", "toString", "H", "Serializable", "h", "Product", "Any", "runtime", "matchEnd3", "String", "T0") + val expectedNamesForHNil = Set( + "x", + "HNil", + "ScalaRunTime", + "Iterator", + "Boolean", + "A", + "T", + "$" + "isInstanceOf", + "::", + "Nothing", + "x$1", + "acme", + "typedProductIterator", + "Int", + "", + "apply", + "Object", + "IndexOutOfBoundsException", + "scala", + "HList", + "toString", + "H", + "Serializable", + "h", + "Product", + "Any", + "runtime", + "matchEnd3", + "String", + "T0" + ) val expectedNamesForHList = Set("Tupler", "acme", "scala", "Serializable", "Product") assert(usedNames("acme.Tupler") -- scalaDiff === expectedNamesForTupler -- scalaDiff) - assert(usedNames("acme.TuplerInstances") -- scalaDiff === expectedNamesForTuplerInstances -- scalaDiff) - assert(usedNames("acme.TuplerInstances.") -- scalaDiff === expectedNamesForRefinement -- scalaDiff) + assert( + usedNames("acme.TuplerInstances") -- scalaDiff === expectedNamesForTuplerInstances -- scalaDiff) + assert( + usedNames("acme.TuplerInstances.") -- scalaDiff === expectedNamesForRefinement -- scalaDiff) assert(usedNames("acme.$colon$colon") -- scalaDiff === `expectedNamesFor::` -- scalaDiff) assert(usedNames("acme.DepFn1") -- scalaDiff === expectedNamesForDepFn1 -- scalaDiff) assert(usedNames("acme.HNil") -- scalaDiff === expectedNamesForHNil -- scalaDiff) @@ -69,10 +352,13 @@ class ExtractUsedNamesPerformanceSpecification extends UnitSpec { |}""".stripMargin val compilerForTesting = new ScalaCompilerForUnitTesting val usedNames = compilerForTesting.extractUsedNamesFromSrc(src) - val expectedNamesForTuplerInstances = Set("Tupler", "AnyRef", "L", "Out0", "scala", "HList", "Object") + val expectedNamesForTuplerInstances = + Set("Tupler", "AnyRef", "L", "Out0", "scala", "HList", "Object") val expectedNamesForTuplerInstancesRefinement = Set("Out0") - assert(usedNames("TuplerInstances") -- scalaDiff === expectedNamesForTuplerInstances -- scalaDiff) - assert(usedNames("TuplerInstances.") -- scalaDiff === expectedNamesForTuplerInstancesRefinement -- scalaDiff) + assert( + usedNames("TuplerInstances") -- scalaDiff === expectedNamesForTuplerInstances -- scalaDiff) + assert( + usedNames("TuplerInstances.") -- scalaDiff === expectedNamesForTuplerInstancesRefinement -- scalaDiff) } it should "correctly collect used names from macro extension" in { @@ -93,12 +379,108 @@ class ExtractUsedNamesPerformanceSpecification extends UnitSpec { | def bar[Out] = macro Foo.foo_impl[Out] |}""".stripMargin val compilerForTesting = new ScalaCompilerForUnitTesting - val (_, analysis) = compilerForTesting.compileSrcs(List(List(ext), List(cod)), reuseCompilerInstance = true) + val (_, analysis) = + compilerForTesting.compileSrcs(List(List(ext), List(cod)), reuseCompilerInstance = true) val usedNames = analysis.usedNames.toMap - val expectedNamesForFoo = Set("TypeApplyExtractor", "mkIdent", "package", "", "tpe", "in", "$u", "internal", "reify", "WeakTypeTag", "Name", "empty", "collection", "ThisType", "staticModule", "staticPackage", "Singleton", "T", "asInstanceOf", "ReificationSupportApi", "U", "Expr", "Universe", "TypeApply", "A", "Tree", "Nothing", "acme", "ClassSymbol", "blackbox", "AnyRef", "Context", "mkTypeTree", "immutable", "SelectExtractor", "", "$treecreator1", "apply", "Object", "macros", "moduleClass", "Foo", "T0", "Symbol", "Predef", "scala", "asModule", "Internal", "$m", "TypeCreator", "TermNameExtractor", "ModuleSymbol", "staticClass", "universe", "c", "", "TypeTree", "List", "Select", "TermName", "Mirror", "atag", "reificationSupport", "rootMirror", "reflect", "TypeRef", "Ident", "Any", "TreeCreator", "$typecreator2", "$m$untyped", "String", "Type") - val expectedNamesForBar = Set("experimental", "package", "WeakTypeTag", "Out", "foo_impl", "Expr", "A", "Nothing", "acme", "AnyRef", "Context", "", "language", "Object", "macros", "Bar", "Foo", "scala", "List", "Any") + val expectedNamesForFoo = Set( + "TypeApplyExtractor", + "mkIdent", + "package", + "", + "tpe", + "in", + "$u", + "internal", + "reify", + "WeakTypeTag", + "Name", + "empty", + "collection", + "ThisType", + "staticModule", + "staticPackage", + "Singleton", + "T", + "asInstanceOf", + "ReificationSupportApi", + "U", + "Expr", + "Universe", + "TypeApply", + "A", + "Tree", + "Nothing", + "acme", + "ClassSymbol", + "blackbox", + "AnyRef", + "Context", + "mkTypeTree", + "immutable", + "SelectExtractor", + "", + "$treecreator1", + "apply", + "Object", + "macros", + "moduleClass", + "Foo", + "T0", + "Symbol", + "Predef", + "scala", + "asModule", + "Internal", + "$m", + "TypeCreator", + "TermNameExtractor", + "ModuleSymbol", + "staticClass", + "universe", + "c", + "", + "TypeTree", + "List", + "Select", + "TermName", + "Mirror", + "atag", + "reificationSupport", + "rootMirror", + "reflect", + "TypeRef", + "Ident", + "Any", + "TreeCreator", + "$typecreator2", + "$m$untyped", + "String", + "Type" + ) + val expectedNamesForBar = Set( + "experimental", + "package", + "WeakTypeTag", + "Out", + "foo_impl", + "Expr", + "A", + "Nothing", + "acme", + "AnyRef", + "Context", + "", + "language", + "Object", + "macros", + "Bar", + "Foo", + "scala", + "List", + "Any" + ) assert(usedNames("acme.Foo") === expectedNamesForFoo) assert(usedNames("acme.Bar") === expectedNamesForBar) } -} \ No newline at end of file +} diff --git a/src/test/scala/xsbt/ExtractUsedNamesSpecification.scala b/src/test/scala/xsbt/ExtractUsedNamesSpecification.scala index 5eff3182f8e4..19aa8837eb33 100644 --- a/src/test/scala/xsbt/ExtractUsedNamesSpecification.scala +++ b/src/test/scala/xsbt/ExtractUsedNamesSpecification.scala @@ -130,10 +130,25 @@ class ExtractUsedNamesSpecification extends UnitSpec { val expectedNames_lista = standardNames ++ Set("Test_lista", "x", "B", "lista", "List", "A") val expectedNames_at = standardNames ++ Set("Test_at", "x", "B", "at", "A", "T", "X0", "X1") val expectedNames_as = standardNames ++ Set("Test_as", "x", "B", "as", "S", "Y") - val expectedNames_foo = standardNames ++ Set("Test_foo", "x", "B", "foo", "M", "N", - "Predef", "???", "Nothing") - val expectedNames_bar = standardNames ++ Set("Test_bar", "x", "B", "bar", "Param", "P1", "P0", - "Predef", "???", "Nothing") + val expectedNames_foo = standardNames ++ Set("Test_foo", + "x", + "B", + "foo", + "M", + "N", + "Predef", + "???", + "Nothing") + val expectedNames_bar = standardNames ++ Set("Test_bar", + "x", + "B", + "bar", + "Param", + "P1", + "P0", + "Predef", + "???", + "Nothing") assert(usedNames("Test_lista") === expectedNames_lista) assert(usedNames("Test_at") === expectedNames_at) assert(usedNames("Test_as") === expectedNames_as) @@ -150,12 +165,22 @@ class ExtractUsedNamesSpecification extends UnitSpec { """.stripMargin val compilerForTesting = new ScalaCompilerForUnitTesting val usedNames = compilerForTesting.extractUsedNamesFromSrc(srcFoo) - val expectedNames = standardNames ++ Seq("Double", "Foo", "T", "foo", "scala", "language", "existentials", "Nothing", "???", "Predef") + val expectedNames = standardNames ++ Seq("Double", + "Foo", + "T", + "foo", + "scala", + "language", + "existentials", + "Nothing", + "???", + "Predef") assert(usedNames("Foo") === expectedNames) } it should "extract used names from a refinement" in { - val srcFoo = "object Outer {\n class Inner { type Xyz }\n\n type TypeInner = Inner { type Xyz = Int }\n}" + val srcFoo = + "object Outer {\n class Inner { type Xyz }\n\n type TypeInner = Inner { type Xyz = Int }\n}" val srcBar = "object Bar {\n def bar: Outer.TypeInner = null\n}" val compilerForTesting = new ScalaCompilerForUnitTesting val usedNames = compilerForTesting.extractUsedNamesFromSrc(srcFoo, srcBar) @@ -209,7 +234,8 @@ class ExtractUsedNamesSpecification extends UnitSpec { def findPatMatUsages(in: String): Set[String] = { val compilerForTesting = new ScalaCompilerForUnitTesting - val (_, callback) = compilerForTesting.compileSrcs(List(List(sealedClass, in)), reuseCompilerInstance = false) + val (_, callback) = + compilerForTesting.compileSrcs(List(List(sealedClass, in)), reuseCompilerInstance = false) val clientNames = callback.usedNamesAndScopes.filterKeys(!_.startsWith("base.")) val names: Set[String] = clientNames.flatMap { @@ -233,9 +259,12 @@ class ExtractUsedNamesSpecification extends UnitSpec { findPatMatUsages(classWithPatMatOfType()) shouldEqual Set(sealedClassName) // Option is sealed - findPatMatUsages(classWithPatMatOfType(s"Option[$sealedClassName]")) shouldEqual Set(sealedClassName, "Option") + findPatMatUsages(classWithPatMatOfType(s"Option[$sealedClassName]")) shouldEqual Set( + sealedClassName, + "Option") // Seq and Set is not - findPatMatUsages(classWithPatMatOfType(s"Seq[Set[$sealedClassName]]")) shouldEqual Set(sealedClassName) + findPatMatUsages(classWithPatMatOfType(s"Seq[Set[$sealedClassName]]")) shouldEqual Set( + sealedClassName) def inNestedCase(tpe: String) = s"""package client @@ -270,7 +299,8 @@ class ExtractUsedNamesSpecification extends UnitSpec { private val standardNames = Set( "scala", // The default parent of a class is "AnyRef" which is an alias for "Object" - "AnyRef", "Object", + "AnyRef", + "Object", // class receives a default constructor which is internally called "" "" ) diff --git a/src/test/scala/xsbt/ScalaCompilerForUnitTesting.scala b/src/test/scala/xsbt/ScalaCompilerForUnitTesting.scala index 7760da25bbfd..059bcedf1582 100644 --- a/src/test/scala/xsbt/ScalaCompilerForUnitTesting.scala +++ b/src/test/scala/xsbt/ScalaCompilerForUnitTesting.scala @@ -29,7 +29,8 @@ class ScalaCompilerForUnitTesting { * Compiles given source code using Scala compiler and returns API representation * extracted by ExtractAPI class. */ - def extractApisFromSrcs(reuseCompilerInstance: Boolean)(srcs: List[String]*): Seq[Set[ClassLike]] = { + def extractApisFromSrcs(reuseCompilerInstance: Boolean)( + srcs: List[String]*): Seq[Set[ClassLike]] = { val (tempSrcFiles, analysisCallback) = compileSrcs(srcs.toList, reuseCompilerInstance) tempSrcFiles.map(analysisCallback.apis) } @@ -53,9 +54,9 @@ class ScalaCompilerForUnitTesting { * Run but only names used in the second src file are returned. */ def extractUsedNamesFromSrc( - definitionSrc: String, - actualSrc: String, - assertDefaultScope: Boolean = true + definitionSrc: String, + actualSrc: String, + assertDefaultScope: Boolean = true ): Map[String, Set[String]] = { // we drop temp src file corresponding to the definition src file val (Seq(_, tempSrcFile), analysisCallback) = compileSrcs(definitionSrc, actualSrc) @@ -77,10 +78,12 @@ class ScalaCompilerForUnitTesting { */ def extractUsedNamesFromSrc(sources: String*): Map[String, Set[String]] = { val (srcFiles, analysisCallback) = compileSrcs(sources: _*) - srcFiles.map { srcFile => - val classesInSrc = analysisCallback.classNames(srcFile).map(_._1) - classesInSrc.map(className => className -> analysisCallback.usedNames(className)).toMap - }.reduce(_ ++ _) + srcFiles + .map { srcFile => + val classesInSrc = analysisCallback.classNames(srcFile).map(_._1) + classesInSrc.map(className => className -> analysisCallback.usedNames(className)).toMap + } + .reduce(_ ++ _) } /** @@ -131,22 +134,25 @@ class ScalaCompilerForUnitTesting { * callback is returned as a result. */ private[xsbt] def compileSrcs( - groupedSrcs: List[List[String]], - reuseCompilerInstance: Boolean + groupedSrcs: List[List[String]], + reuseCompilerInstance: Boolean ): (Seq[File], TestCallback) = { withTemporaryDirectory { temp => val analysisCallback = new TestCallback val classesDir = new File(temp, "classes") classesDir.mkdir() - lazy val commonCompilerInstance = prepareCompiler(classesDir, analysisCallback, classesDir.toString) + lazy val commonCompilerInstance = + prepareCompiler(classesDir, analysisCallback, classesDir.toString) val files = for ((compilationUnit, unitId) <- groupedSrcs.zipWithIndex) yield { // use a separate instance of the compiler for each group of sources to // have an ability to test for bugs in instability between source and pickled // representation of types - val compiler = if (reuseCompilerInstance) commonCompilerInstance else - prepareCompiler(classesDir, analysisCallback, classesDir.toString) + val compiler = + if (reuseCompilerInstance) commonCompilerInstance + else + prepareCompiler(classesDir, analysisCallback, classesDir.toString) val run = new compiler.Run val srcFiles = compilationUnit.zipWithIndex map { case (src, i) => @@ -174,7 +180,9 @@ class ScalaCompilerForUnitTesting { srcFile } - private[xsbt] def prepareCompiler(outputDir: File, analysisCallback: AnalysisCallback, classpath: String = "."): CachedCompiler0#Compiler = { + private[xsbt] def prepareCompiler(outputDir: File, + analysisCallback: AnalysisCallback, + classpath: String = "."): CachedCompiler0#Compiler = { val args = Array.empty[String] object output extends SingleOutput { def outputDirectory: File = outputDir @@ -203,4 +211,3 @@ class ScalaCompilerForUnitTesting { } } -