diff --git a/compile/api/src/main/scala/xsbt/api/ShowAPI.scala b/compile/api/src/main/scala/xsbt/api/ShowAPI.scala index 4412be75d..ab3b4f61b 100644 --- a/compile/api/src/main/scala/xsbt/api/ShowAPI.scala +++ b/compile/api/src/main/scala/xsbt/api/ShowAPI.scala @@ -5,284 +5,147 @@ package xsbt.api import xsbti.api._ -trait Show[A] { - def show(a: A): String -} +import scala.util.Try -final class ShowLazy[A](delegate: => Show[A]) extends Show[A] { - private lazy val s = delegate - def show(a: A) = s.show(a) -} +object DefaultShowAPI { + private lazy val defaultNesting = Try { java.lang.Integer.parseInt(sys.props.get("sbt.inc.apidiff.depth").get) } getOrElse 2 -import ShowAPI._ + def apply(d: Definition) = ShowAPI.showDefinition(d)(defaultNesting) + def apply(d: Type) = ShowAPI.showType(d)(defaultNesting) + def apply(a: SourceAPI) = ShowAPI.showApi(a)(defaultNesting) +} object ShowAPI { - def Show[T](implicit s: Show[T]): Show[T] = s - def show[T](t: T)(implicit s: Show[T]): String = s.show(t) + private lazy val numDecls = Try { java.lang.Integer.parseInt(sys.props.get("sbt.inc.apidiff.decls").get) } getOrElse 0 - def bounds(lower: Type, upper: Type)(implicit t: Show[Type]): String = - ">: " + t.show(lower) + " <: " + t.show(upper) + private def truncateDecls(decls: Array[Definition]): Array[Definition] = if (numDecls <= 0) decls else decls.take(numDecls) + private def lines(ls: Seq[String]): String = ls.mkString("\n", "\n", "\n") - import ParameterModifier._ - def parameterModifier(base: String, pm: ParameterModifier): String = - pm match { - case Plain => base - case Repeated => base + "*" - case ByName => "=> " + base + def showApi(a: SourceAPI)(implicit nesting: Int) = + a.packages.map(pkg => "package " + pkg.name).mkString("\n") + lines(truncateDecls(a.definitions).map(showDefinition)) + + def showDefinition(d: Definition)(implicit nesting: Int): String = d match { + case v: Val => showMonoDef(v, "val") + ": " + showType(v.tpe) + case v: Var => showMonoDef(v, "var") + ": " + showType(v.tpe) + case d: Def => showPolyDef(d, "def") + showValueParams(d.valueParameters) + ": " + showType(d.returnType) + case ta: TypeAlias => showPolyDef(ta, "type") + " = " + showType(ta.tpe) + case td: TypeDeclaration => showPolyDef(td, "type") + showBounds(td.lowerBound, td.upperBound) + case cl: ClassLike => showPolyDef(cl, showDefinitionType(cl.definitionType)) + " extends " + showTemplate(cl) + } + + private def showTemplate(cl: ClassLike)(implicit nesting: Int) = + if (nesting <= 0) "" + else { + val showSelf = if (cl.selfType.isInstanceOf[EmptyType]) "" else " self: " + showNestedType(cl.selfType) + " =>" + + cl.structure.parents.map(showNestedType).mkString("", " with ", " {") + showSelf + + lines(truncateDecls(cl.structure.inherited).map(d => "^inherited^ " + showNestedDefinition(d))) + + lines(truncateDecls(cl.structure.declared).map(showNestedDefinition)) + + "}" } - def concat[A](list: Seq[A], as: Show[A], sep: String): String = mapSeq(list, as).mkString(sep) - def commas[A](list: Seq[A], as: Show[A]): String = concat(list, as, ", ") - def spaced[A](list: Seq[A], as: Show[A]): String = concat(list, as, " ") - def lines[A](list: Seq[A], as: Show[A]): String = mapSeq(list, as).mkString("\n") - def mapSeq[A](list: Seq[A], as: Show[A]): Seq[String] = list.map(as.show) + def showType(t: Type)(implicit nesting: Int): String = t match { + case st: Projection => showType(st.prefix) + "#" + st.id + case st: ParameterRef => "<" + st.id + ">" + case st: Singleton => showPath(st.path) + case st: EmptyType => "" + case p: Parameterized => showType(p.baseType) + p.typeArguments.map(showType).mkString("[", ", ", "]") + case c: Constant => showType(c.baseType) + "(" + c.value + ")" + case a: Annotated => showAnnotations(a.annotations) + " " + showType(a.baseType) + case s: Structure => + s.parents.map(showType).mkString(" with ") + ( + if (nesting <= 0) "{ }" + else truncateDecls(s.declared).map(showNestedDefinition).mkString(" {", "\n", "}")) + case e: Existential => + showType(e.baseType) + ( + if (nesting <= 0) " forSome { }" + else e.clause.map(t => "type " + showNestedTypeParameter(t)).mkString(" forSome { ", "; ", " }")) + case p: Polymorphic => showType(p.baseType) + ( + if (nesting <= 0) " [ ]" + else showNestedTypeParameters(p.parameters)) + } + + private def showPath(p: Path): String = p.components.map(showPathComponent).mkString(".") + private def showPathComponent(pc: PathComponent) = pc match { + case s: Super => "super[" + showPath(s.qualifier) + "]" + case _: This => "this" + case i: Id => i.id + } + + private def space(s: String) = if (s.isEmpty) s else s + " " + private def showMonoDef(d: Definition, label: String)(implicit nesting: Int): String = + space(showAnnotations(d.annotations)) + space(showAccess(d.access)) + space(showModifiers(d.modifiers)) + space(label) + d.name + + private def showPolyDef(d: ParameterizedDefinition, label: String)(implicit nesting: Int): String = + showMonoDef(d, label) + showTypeParameters(d.typeParameters) + + private def showTypeParameters(tps: Seq[TypeParameter])(implicit nesting: Int): String = + if (tps.isEmpty) "" + else tps.map(showTypeParameter).mkString("[", ", ", "]") + + private def showTypeParameter(tp: TypeParameter)(implicit nesting: Int): String = + showAnnotations(tp.annotations) + " " + showVariance(tp.variance) + tp.id + showTypeParameters(tp.typeParameters) + " " + showBounds(tp.lowerBound, tp.upperBound) + + private def showAnnotations(as: Seq[Annotation])(implicit nesting: Int) = as.map(showAnnotation).mkString(" ") + private def showAnnotation(a: Annotation)(implicit nesting: Int) = + "@" + showType(a.base) + ( + if (a.arguments.isEmpty) "" + else a.arguments.map(a => a.name + " = " + a.value).mkString("(", ", ", ")") + ) + + private def showBounds(lower: Type, upper: Type)(implicit nesting: Int): String = ">: " + showType(lower) + " <: " + showType(upper) + + private def showValueParams(ps: Seq[ParameterList])(implicit nesting: Int): String = + ps.map(pl => + pl.parameters.map(mp => + mp.name + ": " + showParameterModifier(showType(mp.tpe), mp.modifier) + (if (mp.hasDefault) "= ..." else "") + ).mkString(if (pl.isImplicit) "(implicit " else "(", ", ", ")") + ).mkString("") + + private def showParameterModifier(base: String, pm: ParameterModifier): String = pm match { + case ParameterModifier.Plain => base + case ParameterModifier.Repeated => base + "*" + case ParameterModifier.ByName => "=> " + base + } + + private def showDefinitionType(d: DefinitionType) = d match { + case DefinitionType.Trait => "trait" + case DefinitionType.ClassDef => "class" + case DefinitionType.Module => "object" + case DefinitionType.PackageModule => "package object" + } + + private def showAccess(a: Access) = a match { + case p: Public => "" + case p: Protected => "protected" + showQualifier(p.qualifier) + case p: Private => "private" + showQualifier(p.qualifier) + } + + private def showQualifier(q: Qualifier) = q match { + case _: Unqualified => "" + case _: ThisQualifier => "[this]" + case i: IdQualifier => "[" + i.value + "]" + } + + private def showModifiers(m: Modifiers) = List( + (m.isOverride, "override"), + (m.isFinal, "final"), + (m.isSealed, "sealed"), + (m.isImplicit, "implicit"), + (m.isAbstract, "abstract"), + (m.isLazy, "lazy") + ).collect { case (true, mod) => mod }.mkString(" ") + + private def showVariance(v: Variance) = v match { + case Variance.Invariant => "" + case Variance.Covariant => "+" + case Variance.Contravariant => "-" + } + + // limit nesting to prevent cycles and generally keep output from getting humongous + private def showNestedType(tp: Type)(implicit nesting: Int) = showType(tp)(nesting - 1) + private def showNestedTypeParameter(tp: TypeParameter)(implicit nesting: Int) = showTypeParameter(tp)(nesting - 1) + private def showNestedTypeParameters(tps: Seq[TypeParameter])(implicit nesting: Int) = showTypeParameters(tps)(nesting - 1) + private def showNestedDefinition(d: Definition)(implicit nesting: Int) = showDefinition(d)(nesting - 1) } -trait ShowBase { - implicit def showAnnotation(implicit as: Show[AnnotationArgument], t: Show[Type]): Show[Annotation] = - new Show[Annotation] { def show(a: Annotation) = "@" + t.show(a.base) + (if (a.arguments.isEmpty) "" else "(" + commas(a.arguments, as) + ")") } - - implicit def showAnnotationArgument: Show[AnnotationArgument] = - new Show[AnnotationArgument] { def show(a: AnnotationArgument) = a.name + " = " + a.value } - - import Variance._ - implicit def showVariance: Show[Variance] = - new Show[Variance] { def show(v: Variance) = v match { case Invariant => ""; case Covariant => "+"; case Contravariant => "-" } } - - implicit def showSource(implicit ps: Show[Package], ds: Show[Definition]): Show[SourceAPI] = - new Show[SourceAPI] { def show(a: SourceAPI) = lines(a.packages, ps) + "\n" + lines(a.definitions, ds) } - - implicit def showPackage: Show[Package] = - new Show[Package] { def show(pkg: Package) = "package " + pkg.name } - - implicit def showAccess(implicit sq: Show[Qualified]): Show[Access] = - new Show[Access] { - def show(a: Access) = - a match { - case p: Public => "" - case q: Qualified => sq.show(q) - } - } - implicit def showQualified(implicit sq: Show[Qualifier]): Show[Qualified] = - new Show[Qualified] { - def show(q: Qualified) = - ((q match { - case p: Protected => "protected" - case p: Private => "private" - }) - + sq.show(q.qualifier)) - } - implicit def showQualifier: Show[Qualifier] = - new Show[Qualifier] { - def show(q: Qualifier) = - q match { - case _: Unqualified => "" - case _: ThisQualifier => "[this]" - case i: IdQualifier => "[" + i.value + "]" - } - } - implicit def showModifiers: Show[Modifiers] = - new Show[Modifiers] { - def show(m: Modifiers) = - { - val mods = - (m.isOverride, "override") :: - (m.isFinal, "final") :: - (m.isSealed, "sealed") :: - (m.isImplicit, "implicit") :: - (m.isAbstract, "abstract") :: - (m.isLazy, "lazy") :: - Nil - mods.filter(_._1).map(_._2).mkString(" ") - } - } - - implicit def showDefinitionType: Show[DefinitionType] = - new Show[DefinitionType] { - import DefinitionType._ - def show(dt: DefinitionType) = - dt match { - case Trait => "trait" - case ClassDef => "class" - case Module => "object" - case PackageModule => "package object" - } - } -} -trait ShowDefinitions { - implicit def showVal(implicit acs: Show[Access], ms: Show[Modifiers], ans: Show[Annotation], t: Show[Type]): Show[Val] = - new Show[Val] { def show(v: Val) = definitionBase(v, "val")(acs, ms, ans) + ": " + t.show(v.tpe) } - - implicit def showVar(implicit acs: Show[Access], ms: Show[Modifiers], ans: Show[Annotation], t: Show[Type]): Show[Var] = - new Show[Var] { def show(v: Var) = definitionBase(v, "var")(acs, ms, ans) + ": " + t.show(v.tpe) } - - implicit def showDef(implicit acs: Show[Access], ms: Show[Modifiers], ans: Show[Annotation], tp: Show[Seq[TypeParameter]], vp: Show[Seq[ParameterList]], t: Show[Type]): Show[Def] = - new Show[Def] { def show(d: Def) = parameterizedDef(d, "def")(acs, ms, ans, tp) + vp.show(d.valueParameters) + ": " + t.show(d.returnType) } - - implicit def showClassLike(implicit acs: Show[Access], ms: Show[Modifiers], ans: Show[Annotation], tp: Show[Seq[TypeParameter]], dt: Show[DefinitionType], s: Show[Structure], t: Show[Type]): Show[ClassLike] = - new Show[ClassLike] { def show(cl: ClassLike) = parameterizedDef(cl, dt.show(cl.definitionType))(acs, ms, ans, tp) + " requires " + t.show(cl.selfType) + " extends " + s.show(cl.structure) } - - implicit def showTypeAlias(implicit acs: Show[Access], ms: Show[Modifiers], ans: Show[Annotation], tp: Show[Seq[TypeParameter]], t: Show[Type]): Show[TypeAlias] = - new Show[TypeAlias] { def show(ta: TypeAlias) = parameterizedDef(ta, "type")(acs, ms, ans, tp) + " = " + t.show(ta.tpe) } - - implicit def showTypeDeclaration(implicit acs: Show[Access], ms: Show[Modifiers], ans: Show[Annotation], tp: Show[Seq[TypeParameter]], t: Show[Type]): Show[TypeDeclaration] = - new Show[TypeDeclaration] { def show(td: TypeDeclaration) = parameterizedDef(td, "type")(acs, ms, ans, tp) + bounds(td.lowerBound, td.upperBound) } - def showClassLikeSimple(implicit acs: Show[Access], ms: Show[Modifiers], ans: Show[Annotation], tp: Show[Seq[TypeParameter]], dt: Show[DefinitionType]): Show[ClassLike] = - new Show[ClassLike] { def show(cl: ClassLike) = parameterizedDef(cl, dt.show(cl.definitionType))(acs, ms, ans, tp) } - - def parameterizedDef(d: ParameterizedDefinition, label: String)(implicit acs: Show[Access], ms: Show[Modifiers], ans: Show[Annotation], tp: Show[Seq[TypeParameter]]): String = - definitionBase(d, label)(acs, ms, ans) + tp.show(d.typeParameters) - def definitionBase(d: Definition, label: String)(implicit acs: Show[Access], ms: Show[Modifiers], ans: Show[Annotation]): String = - space(spaced(d.annotations, ans)) + space(acs.show(d.access)) + space(ms.show(d.modifiers)) + space(label) + d.name - def space(s: String) = if (s.isEmpty) s else s + " " -} -trait ShowDefinition { - implicit def showDefinition(implicit vl: Show[Val], vr: Show[Var], ds: Show[Def], cl: Show[ClassLike], ta: Show[TypeAlias], td: Show[TypeDeclaration]): Show[Definition] = - new Show[Definition] { - def show(d: Definition) = - d match { - case v: Val => vl.show(v) - case v: Var => vr.show(v) - case d: Def => ds.show(d) - case c: ClassLike => cl.show(c) - case t: TypeAlias => ta.show(t) - case t: TypeDeclaration => td.show(t) - } - } -} -trait ShowType { - implicit def showType(implicit s: Show[SimpleType], a: Show[Annotated], st: Show[Structure], c: Show[Constant], e: Show[Existential], po: Show[Polymorphic]): Show[Type] = - new Show[Type] { - def show(t: Type) = - t match { - case q: SimpleType => s.show(q) - case q: Constant => c.show(q) - case q: Annotated => a.show(q) - case q: Structure => st.show(q) - case q: Existential => e.show(q) - case q: Polymorphic => po.show(q) - } - } - - implicit def showSimpleType(implicit pr: Show[Projection], pa: Show[ParameterRef], si: Show[Singleton], et: Show[EmptyType], p: Show[Parameterized]): Show[SimpleType] = - new Show[SimpleType] { - def show(t: SimpleType) = - t match { - case q: Projection => pr.show(q) - case q: ParameterRef => pa.show(q) - case q: Singleton => si.show(q) - case q: EmptyType => et.show(q) - case q: Parameterized => p.show(q) - } - } -} -trait ShowBasicTypes { - implicit def showSingleton(implicit p: Show[Path]): Show[Singleton] = - new Show[Singleton] { def show(s: Singleton) = p.show(s.path) } - implicit def showEmptyType: Show[EmptyType] = - new Show[EmptyType] { def show(e: EmptyType) = "" } - implicit def showParameterRef: Show[ParameterRef] = - new Show[ParameterRef] { def show(p: ParameterRef) = "<" + p.id + ">" } -} -trait ShowTypes { - implicit def showStructure(implicit t: Show[Type], d: Show[Definition]): Show[Structure] = - new Show[Structure] { - def show(s: Structure) = { - // don't show inherited class like definitions to avoid dealing with cycles - val safeInherited = s.inherited.filterNot(_.isInstanceOf[ClassLike]) - val showInherited: Show[Definition] = new Show[Definition] { - def show(deff: Definition): String = "^inherited^ " + d.show(deff) - } - concat(s.parents, t, " with ") + "\n{\n" + lines(safeInherited, showInherited) + "\n" + lines(s.declared, d) + "\n}" - } - } - implicit def showAnnotated(implicit as: Show[Annotation], t: Show[Type]): Show[Annotated] = - new Show[Annotated] { def show(a: Annotated) = spaced(a.annotations, as) + " " + t.show(a.baseType) } - implicit def showProjection(implicit t: Show[SimpleType]): Show[Projection] = - new Show[Projection] { def show(p: Projection) = t.show(p.prefix) + "#" + p.id } - implicit def showParameterized(implicit t: Show[Type]): Show[Parameterized] = - new Show[Parameterized] { def show(p: Parameterized) = t.show(p.baseType) + mapSeq(p.typeArguments, t).mkString("[", ", ", "]") } - implicit def showConstant(implicit t: Show[Type]): Show[Constant] = - new Show[Constant] { def show(c: Constant) = t.show(c.baseType) + "(" + c.value + ")" } - implicit def showExistential(implicit t: Show[Type], tp: Show[TypeParameter]): Show[Existential] = - new Show[Existential] { - def show(e: Existential) = - t.show(e.baseType) + e.clause.map(t => "type " + tp.show(t)).mkString(" forSome { ", "; ", "}") - } - implicit def showPolymorphic(implicit t: Show[Type], tps: Show[Seq[TypeParameter]]): Show[Polymorphic] = - new Show[Polymorphic] { def show(p: Polymorphic) = t.show(p.baseType) + tps.show(p.parameters) } - -} - -trait ShowPath { - implicit def showPath(implicit pc: Show[PathComponent]): Show[Path] = - new Show[Path] { def show(p: Path) = mapSeq(p.components, pc).mkString(".") } - - implicit def showPathComponent(implicit sp: Show[Path]): Show[PathComponent] = - new Show[PathComponent] { - def show(p: PathComponent) = - p match { - case s: Super => "super[" + sp.show(s.qualifier) + "]" - case _: This => "this" - case i: Id => i.id - } - } -} - -trait ShowValueParameters { - implicit def showParameterLists(implicit pl: Show[ParameterList]): Show[Seq[ParameterList]] = - new Show[Seq[ParameterList]] { def show(p: Seq[ParameterList]) = concat(p, pl, "") } - implicit def showParameterList(implicit mp: Show[MethodParameter]): Show[ParameterList] = - new Show[ParameterList] { def show(pl: ParameterList) = "(" + (if (pl.isImplicit) "implicit " else "") + commas(pl.parameters, mp) + ")" } - - implicit def showMethodParameter(implicit t: Show[Type]): Show[MethodParameter] = - new Show[MethodParameter] { - def show(mp: MethodParameter) = - mp.name + ": " + parameterModifier(t.show(mp.tpe), mp.modifier) + (if (mp.hasDefault) "= ..." else "") - } -} -trait ShowTypeParameters { - implicit def showTypeParameters(implicit as: Show[TypeParameter]): Show[Seq[TypeParameter]] = - new Show[Seq[TypeParameter]] { def show(tps: Seq[TypeParameter]) = if (tps.isEmpty) "" else mapSeq(tps, as).mkString("[", ",", "]") } - implicit def showTypeParameter(implicit as: Show[Annotation], tp: Show[Seq[TypeParameter]], t: Show[Type], v: Show[Variance]): Show[TypeParameter] = - new Show[TypeParameter] { - def show(tps: TypeParameter) = - spaced(tps.annotations, as) + " " + v.show(tps.variance) + tps.id + tp.show(tps.typeParameters) + " " + bounds(tps.lowerBound, tps.upperBound) - } -} - -// this class is a hack to resolve some diverging implicit errors. -// I'm pretty sure the cause is the Show[Seq[T]] dominating Show[X] issue. -// It could probably be reduced a bit if that is the case (below was trial and error) -object DefaultShowAPI extends ShowBase with ShowBasicTypes with ShowValueParameters { - def apply(d: Definition) = ShowAPI.show(d) - def apply(d: Type) = ShowAPI.show(d) - - implicit lazy val showVal: Show[Val] = Cyclic.showVal - implicit lazy val showVar: Show[Var] = Cyclic.showVar - implicit lazy val showClassLike: Show[ClassLike] = Cyclic.showClassLike - implicit lazy val showTypeDeclaration: Show[TypeDeclaration] = Cyclic.showTypeDeclaration - implicit lazy val showTypeAlias: Show[TypeAlias] = Cyclic.showTypeAlias - implicit lazy val showDef: Show[Def] = Cyclic.showDef - - implicit lazy val showProj: Show[Projection] = Cyclic.showProjection - implicit lazy val showPoly: Show[Polymorphic] = Cyclic.showPolymorphic - - implicit lazy val showSimple: Show[SimpleType] = new ShowLazy(Cyclic.showSimpleType) - implicit lazy val showAnnotated: Show[Annotated] = Cyclic.showAnnotated - implicit lazy val showExistential: Show[Existential] = Cyclic.showExistential - implicit lazy val showConstant: Show[Constant] = Cyclic.showConstant - implicit lazy val showParameterized: Show[Parameterized] = Cyclic.showParameterized - - implicit lazy val showTypeParameters: Show[Seq[TypeParameter]] = new ShowLazy(Cyclic.showTypeParameters) - implicit lazy val showTypeParameter: Show[TypeParameter] = Cyclic.showTypeParameter - - implicit lazy val showDefinition: Show[Definition] = new ShowLazy(Cyclic.showDefinition) - implicit lazy val showType: Show[Type] = new ShowLazy(Cyclic.showType) - implicit lazy val showStructure: Show[Structure] = new ShowLazy(Cyclic.showStructure) - - implicit lazy val showPath: Show[Path] = new ShowLazy(Cyclic.showPath) - implicit lazy val showPathComponent: Show[PathComponent] = Cyclic.showPathComponent - - private object Cyclic extends ShowTypes with ShowType with ShowPath with ShowDefinition with ShowDefinitions with ShowTypeParameters -} \ No newline at end of file diff --git a/compile/inc/src/main/scala/sbt/inc/APIDiff.scala b/compile/inc/src/main/scala/sbt/inc/APIDiff.scala index 48fa0968f..572b9e34b 100644 --- a/compile/inc/src/main/scala/sbt/inc/APIDiff.scala +++ b/compile/inc/src/main/scala/sbt/inc/APIDiff.scala @@ -1,8 +1,7 @@ package sbt.inc import xsbti.api.SourceAPI -import xsbt.api.ShowAPI -import xsbt.api.DefaultShowAPI._ +import xsbt.api.DefaultShowAPI import java.lang.reflect.Method import java.util.{ List => JList } @@ -39,8 +38,8 @@ private[inc] class APIDiff { * Generates an unified diff between textual representations of `api1` and `api2`. */ def generateApiDiff(fileName: String, api1: SourceAPI, api2: SourceAPI, contextSize: Int): String = { - val api1Str = ShowAPI.show(api1) - val api2Str = ShowAPI.show(api2) + val api1Str = DefaultShowAPI(api1) + val api2Str = DefaultShowAPI(api2) generateApiDiff(fileName, api1Str, api2Str, contextSize) } diff --git a/compile/inc/src/main/scala/sbt/inc/Incremental.scala b/compile/inc/src/main/scala/sbt/inc/Incremental.scala index 5966ea52d..db2cff86e 100644 --- a/compile/inc/src/main/scala/sbt/inc/Incremental.scala +++ b/compile/inc/src/main/scala/sbt/inc/Incremental.scala @@ -20,6 +20,12 @@ import java.io.File * - IncrementalAnyStyle */ object Incremental { + class PrefixingLogger(val prefix: String)(orig: Logger) extends Logger { + def trace(t: => Throwable): Unit = orig.trace(t) + def success(message: => String): Unit = orig.success(message) + def log(level: sbt.Level.Value, message: => String): Unit = orig.log(level, message.replaceAll("(?m)^", prefix)) + } + /** * Runs the incremental compiler algorithm. * @@ -45,7 +51,7 @@ object Incremental { { val incremental: IncrementalCommon = if (options.nameHashing) - new IncrementalNameHashing(log, options) + new IncrementalNameHashing(new PrefixingLogger("[naha] ")(log), options) else if (options.antStyle) new IncrementalAntStyle(log, options) else diff --git a/compile/inc/src/main/scala/sbt/inc/IncrementalCommon.scala b/compile/inc/src/main/scala/sbt/inc/IncrementalCommon.scala index e11939285..e38c68e2c 100644 --- a/compile/inc/src/main/scala/sbt/inc/IncrementalCommon.scala +++ b/compile/inc/src/main/scala/sbt/inc/IncrementalCommon.scala @@ -22,7 +22,8 @@ private[inc] abstract class IncrementalCommon(log: Logger, options: IncOptions) if (invalidatedRaw.isEmpty) previous else { - def debug(s: => String) = if (incDebug(options)) log.debug(s) else () + val wrappedLog = new Incremental.PrefixingLogger("[inv] ")(log) + def debug(s: => String) = if (incDebug(options)) wrappedLog.debug(s) else () val withPackageObjects = invalidatedRaw ++ invalidatedPackageObjects(invalidatedRaw, previous.relations) val invalidated = expand(withPackageObjects, allSources) val pruned = Incremental.prune(invalidated, previous, classfileManager) @@ -64,23 +65,23 @@ private[inc] abstract class IncrementalCommon(log: Logger, options: IncOptions) newAPIMapping: T => Source): Unit = { val contextSize = options.apiDiffContextSize try { + val wrappedLog = new Incremental.PrefixingLogger("[diff] ")(log) val apiDiff = new APIDiff apiChanges foreach { case APIChangeDueToMacroDefinition(src) => - log.debug(s"Public API is considered to be changed because $src contains a macro definition.") + wrappedLog.debug(s"Public API is considered to be changed because $src contains a macro definition.") case apiChange @ (_: SourceAPIChange[T] | _: NamesChange[T]) => val src = apiChange.modified val oldApi = oldAPIMapping(src) val newApi = newAPIMapping(src) val apiUnifiedPatch = apiDiff.generateApiDiff(src.toString, oldApi.api, newApi.api, contextSize) - log.debug(s"Detected a change in a public API (${src.toString}):\n" - + apiUnifiedPatch) + wrappedLog.debug(s"Detected a change in a public API ($src):\n$apiUnifiedPatch") } } catch { case e: ClassNotFoundException => log.error("You have api debugging enabled but DiffUtils library cannot be found on sbt's classpath") case e: LinkageError => - log.error("Encoutared linkage error while trying to load DiffUtils library.") + log.error("Encountered linkage error while trying to load DiffUtils library.") log.trace(e) case e: Exception => log.error("An exception has been thrown while trying to dump an api diff.") diff --git a/compile/interface/src/main/scala/xsbt/Dependency.scala b/compile/interface/src/main/scala/xsbt/Dependency.scala index a72f615a6..80d68c5ba 100644 --- a/compile/interface/src/main/scala/xsbt/Dependency.scala +++ b/compile/interface/src/main/scala/xsbt/Dependency.scala @@ -36,28 +36,26 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile { private class DependencyPhase(prev: Phase) extends Phase(prev) { override def description = "Extracts dependency information" def name = Dependency.name - def run { + def run: Unit = { for (unit <- currentRun.units if !unit.isJava) { // build dependencies structure val sourceFile = unit.source.file.file if (global.callback.nameHashing) { - val dependenciesByMemberRef = extractDependenciesByMemberRef(unit) - for (on <- dependenciesByMemberRef) - processDependency(on, context = DependencyByMemberRef) + val dependencyExtractor = new ExtractDependenciesTraverser + dependencyExtractor.traverse(unit.body) - val dependenciesByInheritance = extractDependenciesByInheritance(unit) - for (on <- dependenciesByInheritance) - processDependency(on, context = DependencyByInheritance) + dependencyExtractor.topLevelDependencies foreach processDependency(context = DependencyByMemberRef) + dependencyExtractor.topLevelInheritanceDependencies foreach processDependency(context = DependencyByInheritance) } else { - for (on <- unit.depends) processDependency(on, context = DependencyByMemberRef) - for (on <- inheritedDependencies.getOrElse(sourceFile, Nil: Iterable[Symbol])) processDependency(on, context = DependencyByInheritance) + unit.depends foreach processDependency(context = DependencyByMemberRef) + inheritedDependencies.getOrElse(sourceFile, Nil: Iterable[Symbol]) foreach processDependency(context = DependencyByInheritance) } /** * Handles dependency on given symbol by trying to figure out if represents a term * that is coming from either source code (not necessarily compiled in this compilation * run) or from class file and calls respective callback method. */ - def processDependency(on: Symbol, context: DependencyContext): Unit = { + def processDependency(context: DependencyContext)(on: Symbol) = { def binaryDependency(file: File, className: String) = callback.binaryDependency(file, className, sourceFile, context) val onSource = on.sourceFile if (onSource == null) { @@ -78,6 +76,91 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile { } } + private class ExtractDependenciesTraverser extends Traverser { + private val _dependencies = collection.mutable.HashSet.empty[Symbol] + protected def addDependency(dep: Symbol): Unit = if (dep ne NoSymbol) _dependencies += dep + def dependencies: Iterator[Symbol] = _dependencies.iterator + def topLevelDependencies: Iterator[Symbol] = _dependencies.map(enclosingTopLevelClass).iterator + + private val _inheritanceDependencies = collection.mutable.HashSet.empty[Symbol] + protected def addInheritanceDependency(dep: Symbol): Unit = if (dep ne NoSymbol) _inheritanceDependencies += dep + def inheritanceDependencies: Iterator[Symbol] = _inheritanceDependencies.iterator + def topLevelInheritanceDependencies: Iterator[Symbol] = _inheritanceDependencies.map(enclosingTopLevelClass).iterator + + /* + * Some macros appear to contain themselves as original tree. + * We must check that we don't inspect the same tree over and over. + * See https://issues.scala-lang.org/browse/SI-8486 + * https://github.com/sbt/sbt/issues/1237 + * https://github.com/sbt/sbt/issues/1544 + */ + private val inspectedOriginalTrees = collection.mutable.Set.empty[Tree] + + override def traverse(tree: Tree): Unit = tree match { + case Import(expr, selectors) => + traverse(expr) + selectors.foreach { + case ImportSelector(nme.WILDCARD, _, null, _) => + // in case of wildcard import we do not rely on any particular name being defined + // on `expr`; all symbols that are being used will get caught through selections + case ImportSelector(name: Name, _, _, _) => + def lookupImported(name: Name) = expr.symbol.info.member(name) + // importing a name means importing both a term and a type (if they exist) + addDependency(lookupImported(name.toTermName)) + addDependency(lookupImported(name.toTypeName)) + } + + /* + * Idents are used in number of situations: + * - to refer to local variable + * - to refer to a top-level package (other packages are nested selections) + * - to refer to a term defined in the same package as an enclosing class; + * this looks fishy, see this thread: + * https://groups.google.com/d/topic/scala-internals/Ms9WUAtokLo/discussion + */ + case id: Ident => addDependency(id.symbol) + case sel @ Select(qual, _) => + traverse(qual); addDependency(sel.symbol) + case sel @ SelectFromTypeTree(qual, _) => + traverse(qual); addDependency(sel.symbol) + + 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) + } + + 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)) + + inheritanceSymbols.foreach(addInheritanceDependency) + + val allSymbols = (inheritanceTypes + self.tpt.tpe).flatMap(symbolsInType) + (allSymbols ++ inheritanceSymbols).foreach(addDependency) + traverseTrees(body) + + // In some cases (eg. macro annotations), `typeTree.tpe` may be null. See sbt/sbt#1593 and sbt/sbt#1655. + case typeTree: TypeTree if typeTree.tpe != null => symbolsInType(typeTree.tpe) foreach addDependency + + case MacroExpansionOf(original) if inspectedOriginalTrees.add(original) => traverse(original) + case other => super.traverse(other) + } + + private def symbolsInType(tp: Type): Set[Symbol] = { + val typeSymbolCollector = + new CollectTypeTraverser({ + case tpe if (tpe != null) && !tpe.typeSymbolDirect.isPackage => tpe.typeSymbolDirect + }) + + typeSymbolCollector.traverse(tp) + typeSymbolCollector.collected.toSet + } + } + /** * Traverses given type and collects result of applying a partial function `pf`. * @@ -94,109 +177,15 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile { } } - private abstract class ExtractDependenciesTraverser extends Traverser { - protected val depBuf = collection.mutable.ArrayBuffer.empty[Symbol] - protected def addDependency(dep: Symbol): Unit = depBuf += dep - def dependencies: collection.immutable.Set[Symbol] = { - // convert to immutable set and remove NoSymbol if we have one - depBuf.toSet - NoSymbol - } - } - - private class ExtractDependenciesByMemberRefTraverser extends ExtractDependenciesTraverser { - - /* - * Some macros appear to contain themselves as original tree. - * We must check that we don't inspect the same tree over and over. - * See https://issues.scala-lang.org/browse/SI-8486 - * https://github.com/sbt/sbt/issues/1237 - * https://github.com/sbt/sbt/issues/1544 - */ - private val inspectedOriginalTrees = collection.mutable.Set.empty[Tree] - - override def traverse(tree: Tree): Unit = { - tree match { - case Import(expr, selectors) => - selectors.foreach { - case ImportSelector(nme.WILDCARD, _, null, _) => - // in case of wildcard import we do not rely on any particular name being defined - // on `expr`; all symbols that are being used will get caught through selections - case ImportSelector(name: Name, _, _, _) => - def lookupImported(name: Name) = expr.symbol.info.member(name) - // importing a name means importing both a term and a type (if they exist) - addDependency(lookupImported(name.toTermName)) - addDependency(lookupImported(name.toTypeName)) - } - case select: Select => - addDependency(select.symbol) - /* - * Idents are used in number of situations: - * - to refer to local variable - * - to refer to a top-level package (other packages are nested selections) - * - to refer to a term defined in the same package as an enclosing class; - * this looks fishy, see this thread: - * https://groups.google.com/d/topic/scala-internals/Ms9WUAtokLo/discussion - */ - case ident: Ident => - addDependency(ident.symbol) - // In some cases (eg. macro annotations), `typeTree.tpe` may be null. - // See sbt/sbt#1593 and sbt/sbt#1655. - case typeTree: TypeTree if typeTree.tpe != null => - val typeSymbolCollector = new CollectTypeTraverser({ - case tpe if !tpe.typeSymbol.isPackage => tpe.typeSymbol - }) - typeSymbolCollector.traverse(typeTree.tpe) - val deps = typeSymbolCollector.collected.toSet - deps.foreach(addDependency) - case Template(parents, self, body) => - traverseTrees(body) - case MacroExpansionOf(original) if inspectedOriginalTrees.add(original) => - this.traverse(original) - case other => () - } - super.traverse(tree) - } - } - - private def extractDependenciesByMemberRef(unit: CompilationUnit): collection.immutable.Set[Symbol] = { - val traverser = new ExtractDependenciesByMemberRefTraverser - traverser.traverse(unit.body) - val dependencies = traverser.dependencies - dependencies.map(enclosingTopLevelClass) - } - /** Copied straight from Scala 2.10 as it does not exist in Scala 2.9 compiler */ - private final def debuglog(msg: => String): Unit = { - if (settings.debug.value) - log(msg) - } - - private final class ExtractDependenciesByInheritanceTraverser extends ExtractDependenciesTraverser { - override def traverse(tree: Tree): Unit = tree match { - case Template(parents, self, body) => - // we are using typeSymbol and not typeSymbolDirect because we want - // type aliases to be expanded - val parentTypeSymbols = parents.map(parent => parent.tpe.typeSymbol).toSet - debuglog("Parent type symbols for " + tree.pos + ": " + parentTypeSymbols.map(_.fullName)) - parentTypeSymbols.foreach(addDependency) - traverseTrees(body) - case tree => super.traverse(tree) - } - } - - private def extractDependenciesByInheritance(unit: CompilationUnit): collection.immutable.Set[Symbol] = { - val traverser = new ExtractDependenciesByInheritanceTraverser - traverser.traverse(unit.body) - val dependencies = traverser.dependencies - dependencies.map(enclosingTopLevelClass) - } + private final def debuglog(msg: => String): Unit = if (settings.debug.value) log(msg) /** * We capture enclosing classes only because that's what CompilationUnit.depends does and we don't want * to deviate from old behaviour too much for now. + * + * NOTE: for Scala 2.8 and 2.9 this method is provided through SymbolCompat */ - private def enclosingTopLevelClass(sym: Symbol): Symbol = - // for Scala 2.8 and 2.9 this method is provided through SymbolCompat - sym.enclosingTopLevelClass + private def enclosingTopLevelClass(sym: Symbol): Symbol = sym.enclosingTopLevelClass } diff --git a/compile/interface/src/main/scala/xsbt/ExtractAPI.scala b/compile/interface/src/main/scala/xsbt/ExtractAPI.scala index 2d94f5d3b..172f8f090 100644 --- a/compile/interface/src/main/scala/xsbt/ExtractAPI.scala +++ b/compile/interface/src/main/scala/xsbt/ExtractAPI.scala @@ -174,13 +174,27 @@ class ExtractAPI[GlobalType <: CallbackGlobal](val global: GlobalType, } private def reference(sym: Symbol): xsbti.api.ParameterRef = new xsbti.api.ParameterRef(tparamID(sym)) - private def annotations(in: Symbol, as: List[AnnotationInfo]): Array[xsbti.api.Annotation] = as.toArray[AnnotationInfo].map(annotation(in, _)) - private def annotation(in: Symbol, a: AnnotationInfo) = - 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] - ) - private def annotated(in: Symbol, as: List[AnnotationInfo], tpe: Type) = new xsbti.api.Annotated(processType(in, tpe), annotations(in, as)) + // 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. + // (When looking at the sources we see all annotations, but when loading from classes we only see the pickled (static) ones.) + private def mkAnnotations(in: Symbol, as: List[AnnotationInfo]): Array[xsbti.api.Annotation] = + 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] + ) + } + + private def annotations(in: Symbol, s: Symbol): Array[xsbti.api.Annotation] = + atPhase(currentRun.typerPhase) { + val base = if (s.hasFlag(Flags.ACCESSOR)) s.accessed else NoSymbol + val b = if (base == NoSymbol) s else base + // 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.getter(b.enclClass), b.setter(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 printMember(label: String, in: Symbol, t: Type) = println(label + " in " + in + " : " + t + " (debug: " + debugString(t) + " )") @@ -329,21 +343,50 @@ class ExtractAPI[GlobalType <: CallbackGlobal](val global: GlobalType, error("Unknown type member" + s) } - private def structure(in: Symbol, s: Symbol): xsbti.api.Structure = structure(viewer(in).memberInfo(s), s, true) - private def structure(info: Type): xsbti.api.Structure = structure(info, info.typeSymbol, false) - private def structure(info: Type, s: Symbol, inherit: Boolean): xsbti.api.Structure = - structureCache.getOrElseUpdate(s, mkStructure(info, s, inherit)) + 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 } - private def mkStructure(info: Type, s: Symbol, inherit: Boolean): xsbti.api.Structure = - { - val (declared, inherited) = info.members.toList.reverse.partition(_.owner == s) - val baseTypes = info.baseClasses.tail.map(info.baseType) - val ds = if (s.isModuleClass) removeConstructors(declared) else declared - val is = if (inherit) removeConstructors(inherited) else Nil - mkStructure(s, baseTypes, ds, is) - } + /** + * Create structure as-is, without embedding ancestors + * + * (for refinement types, and ClassInfoTypes encountered outside of a definition???). + */ + private def mkStructure(info: Type, s: Symbol): xsbti.api.Structure = { + // We're not interested in the full linearization, so we can just use `parents`, + // which side steps issues with baseType when f-bounded existential types and refined types mix + // (and we get cyclic types which cause a stack overflow in showAPI). + // + // The old algorithm's semantics for inherited dependencies include all types occurring as a parent anywhere in a type, + // so that, in `class C { def foo: A }; class A extends B`, C is considered to have an "inherited dependency" on `A` and `B`!!! + val parentTypes = if (global.callback.nameHashing()) info.parents else linearizedAncestorTypes(info) + val decls = info.decls.toList + val declsNoModuleCtor = if (s.isModuleClass) removeConstructors(decls) else decls + mkStructure(s, parentTypes, declsNoModuleCtor, Nil) + } + + /** + * Track all ancestors and inherited members for a class's API. + * + * A class's hash does not include hashes for its parent classes -- only the symbolic names -- + * so we must ensure changes propagate somehow. + * + * TODO: can we include hashes for parent classes instead? This seems a bit messy. + */ + private def mkStructureWithInherited(info: Type, s: Symbol): xsbti.api.Structure = { + val ancestorTypes = linearizedAncestorTypes(info) + val decls = info.decls.toList + val declsNoModuleCtor = if (s.isModuleClass) removeConstructors(decls) else decls + val declSet = decls.toSet + val inherited = info.nonPrivateMembers.toList.filterNot(declSet) // private members are not inherited + mkStructure(s, ancestorTypes, declsNoModuleCtor, inherited) + } + + // Note that the ordering of classes in `baseClasses` is important. + // It would be easier to just say `baseTypeSeq.toList.tail`, + // but that does not take linearization into account. + def linearizedAncestorTypes(info: Type): List[Type] = info.baseClasses.tail.map(info.baseType) // If true, this template is publicly visible and should be processed as a public inheritance dependency. // Local classes and local refinements will never be traversed by the api phase, so we don't need to check for that. @@ -468,7 +511,7 @@ class ExtractAPI[GlobalType <: CallbackGlobal](val global: GlobalType, 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) + structure(withoutRecursiveRefs, sym) case tr @ TypeRef(pre, sym, args) => val base = projectionType(in, pre, sym) if (args.isEmpty) @@ -480,8 +523,12 @@ class ExtractAPI[GlobalType <: CallbackGlobal](val global: GlobalType, 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 => annotatedType(in, at) - case rt: CompoundType => structure(rt) + 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)) @@ -515,9 +562,8 @@ class ExtractAPI[GlobalType <: CallbackGlobal](val global: GlobalType, case x => error("Unknown type parameter info: " + x.getClass) } } - private def tparamID(s: Symbol): String = { - val renameTo = existentialRenamings.renaming(s) - renameTo match { + private def tparamID(s: Symbol): String = + existentialRenamings.renaming(s) match { case Some(rename) => // can't use debuglog because it doesn't exist in Scala 2.9.x if (settings.debug.value) @@ -526,24 +572,38 @@ class ExtractAPI[GlobalType <: CallbackGlobal](val global: GlobalType, case None => s.fullName } - } - private def selfType(in: Symbol, s: Symbol): xsbti.api.Type = processType(in, s.thisSym.typeOfThis) + + + /* Representation for the self type of a class symbol `s`, or `emptyType` for an *unascribed* self variable (or no self variable at all). + Only the self variable's explicitly ascribed type is relevant for incremental compilation. */ + private def selfType(in: Symbol, s: Symbol): xsbti.api.Type = + // `sym.typeOfThis` is implemented as `sym.thisSym.info`, which ensures the *self* symbol is initialized (the type completer is run). + // We can safely avoid running the type completer for `thisSym` for *class* symbols where `thisSym == this`, + // 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.typeOfThis == s.info) Constants.emptyType else processType(in, s.typeOfThis) def classLike(in: Symbol, c: Symbol): ClassLike = classLikeCache.getOrElseUpdate((in, c), mkClassLike(in, c)) - private def mkClassLike(in: Symbol, c: Symbol): ClassLike = - { - val name = c.fullName - val isModule = c.isModuleClass || c.isModule - val struct = if (isModule) c.moduleClass else c - val defType = - if (c.isTrait) DefinitionType.Trait - else if (isModule) { - if (c.isPackage) DefinitionType.PackageModule - else DefinitionType.Module - } else DefinitionType.ClassDef - new xsbti.api.ClassLike(defType, lzy(selfType(in, c)), lzy(structure(in, struct)), emptyStringArray, typeParameters(in, c), name, getAccess(c), getModifiers(c), annotations(in, c)) - } + private def mkClassLike(in: Symbol, c: Symbol): ClassLike = { + // Normalize to a class symbol, and initialize it. + // (An object -- aka module -- also has a term symbol, + // but it's the module class that holds the info about its structure.) + val sym = (if (c.isModule) c.moduleClass else c).initialize + val defType = + if (sym.isTrait) DefinitionType.Trait + else if (sym.isModuleClass) { + if (sym.isPackageClass) DefinitionType.PackageModule + else DefinitionType.Module + } else DefinitionType.ClassDef + new xsbti.api.ClassLike( + defType, lzy(selfType(in, sym)), lzy(structureWithInherited(viewer(in).memberInfo(sym), sym)), emptyStringArray, typeParameters(in, sym), // look at class symbol + c.fullName, getAccess(c), getModifiers(c), annotations(in, c)) // use original symbol (which is a term symbol when `c.isModule`) for `name` and other non-classy stuff + } + + // TODO: could we restrict ourselves to classes, ignoring the term symbol for modules, + // since everything we need to track about a module is in the module's class (`moduleSym.moduleClass`)? private[this] def isClass(s: Symbol) = s.isClass || s.isModule // necessary to ensure a stable ordering of classes in the definitions list: // modules and classes come first and are sorted by name @@ -586,20 +646,10 @@ class ExtractAPI[GlobalType <: CallbackGlobal](val global: GlobalType, n2.toString.trim } - private def annotations(in: Symbol, s: Symbol): Array[xsbti.api.Annotation] = - atPhase(currentRun.typerPhase) { - val base = if (s.hasFlag(Flags.ACCESSOR)) s.accessed else NoSymbol - val b = if (base == NoSymbol) s else base - // 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.getter(b.enclClass), b.setter(b.enclClass)).filter(_ != NoSymbol) - associated.flatMap(ss => annotations(in, ss.annotations)).distinct.toArray; - } - private def annotatedType(in: Symbol, at: AnnotatedType): xsbti.api.Type = - { - val annots = at.annotations - if (annots.isEmpty) processType(in, at.underlying) else annotated(in, annots, at.underlying) - } - + 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 } + implicit def compat(ann: AnnotationInfo): IsStatic = new IsStatic(ann) + annotations.filter(_.isStatic) + } } diff --git a/compile/interface/src/test/scala/xsbt/DependencySpecification.scala b/compile/interface/src/test/scala/xsbt/DependencySpecification.scala index 192d0e000..db6719319 100644 --- a/compile/interface/src/test/scala/xsbt/DependencySpecification.scala +++ b/compile/interface/src/test/scala/xsbt/DependencySpecification.scala @@ -26,7 +26,7 @@ class DependencySpecification extends Specification { inheritance('D) === Set.empty memberRef('E) === Set.empty inheritance('E) === Set.empty - memberRef('F) === Set('A, 'B, 'C, 'D, 'E) + memberRef('F) === Set('A, 'B, 'C, 'D, 'E, 'G) inheritance('F) === Set('A, 'E) memberRef('H) === Set('B, 'E, 'G) // aliases and applied type constructors are expanded so we have inheritance dependency on B @@ -86,8 +86,8 @@ class DependencySpecification extends Specification { |}""".stripMargin val srcD = "class D[T]" val srcE = "trait E[T]" - val srcF = "trait F extends A with E[D[B]] { self: C => }" - val srcG = "object G { type T[x] = B }" + val srcF = "trait F extends A with E[D[B]] { self: G.MyC => }" + val srcG = "object G { type T[x] = B ; type MyC = C }" // T is a type constructor [x]B // B extends D // E verifies the core type gets pulled out diff --git a/notes/0.13.10.markdown b/notes/0.13.10.markdown index 666d9f9cc..c787503cd 100644 --- a/notes/0.13.10.markdown +++ b/notes/0.13.10.markdown @@ -11,6 +11,7 @@ [@fkorotkov]: http://github.com/fkorotkov [@hgiddens]: https://github.com/hgiddens [@DavidPerezIngeniero]: https://github.com/DavidPerezIngeniero + [@romanowski]: https://github.com/romanowski [2302]: https://github.com/sbt/sbt/issues/2302 [2303]: https://github.com/sbt/sbt/pull/2303 [1967]: https://github.com/sbt/sbt/issues/1967 @@ -69,6 +70,7 @@ [1514]: https://github.com/sbt/sbt/issues/1514 [1616]: https://github.com/sbt/sbt/issues/1616 [2313]: https://github.com/sbt/sbt/pull/2313 + [2343]: https://github.com/sbt/sbt/pull/2343 ### Fixes with compatibility implications @@ -94,6 +96,8 @@ - Fixes warnings, and other clean ups. [#2112][2112]/[#2137][2137]/[#2139][2139]/[#2142][2142] by [@pdalpra][@pdalpra] - Adds `localIfFile` to `MavenRepository`, to force artifacts to be copied to the cache. [#2172][2172] by [@dwijnand][@dwijnand] - Adds `Resolver.bintrayIvyRepo(owner, repo)`. [#2285][2285] by [@dwijnand][@dwijnand] +- Non-static annotation changes are no longer tracked by the incremental compiler. [#2343][2343] by [@romanowski][@romanowski] +- Reduces the memory usage of API info extraction in the incremental compiler. [#2343][2343] by [@adriaanm][@adriaanm] ### Bug fixes @@ -122,6 +126,8 @@ - Fixes `JavaErrorParser` to parse non-compile-errors [#2256][2256]/[#2272][2272] by [@Duhemm][@Duhemm] - Fixes task scheduling performance on large builds by skipping checks in `sbt.Execute`. [#2302][2302]/[#2303][2303] by [@jrudolph][@jrudolph] - Fixes launcher configuration to add `sbt-ivy-snapshots` repository to resolve nightly builds. [@eed3si9n][@eed3si9n] +- Fixes performance issues during tree traversal in the incremental compiler. [#2343][2343] by [@adriaanm][@adriaanm] +- Fixes the tracking of self types and F-bounded existential types in the incremental compiler. [#2343][2343] by [@adriaanm][@adriaanm] ### Def.settings diff --git a/sbt/src/sbt-test/api/show-circular-structure/build.sbt b/sbt/src/sbt-test/api/show-circular-structure/build.sbt index 03fb631e4..b88850ee9 100644 --- a/sbt/src/sbt-test/api/show-circular-structure/build.sbt +++ b/sbt/src/sbt-test/api/show-circular-structure/build.sbt @@ -6,7 +6,6 @@ TaskKey[Unit]("show-apis") <<= (compile in Compile, scalaSource in Compile, java val aApi = a.apis.internalAPI(scalaSrc / "A.scala").api val jApi = a.apis.internalAPI(javaSrc / "test/J.java").api import xsbt.api.DefaultShowAPI - import DefaultShowAPI._ - DefaultShowAPI.showSource.show(aApi) - DefaultShowAPI.showSource.show(jApi) + DefaultShowAPI(aApi) + DefaultShowAPI(jApi) } diff --git a/sbt/src/sbt-test/source-dependencies/fbounded-existentials/fbounds.scala b/sbt/src/sbt-test/source-dependencies/fbounded-existentials/fbounds.scala new file mode 100644 index 000000000..60fe40879 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/fbounded-existentials/fbounds.scala @@ -0,0 +1,10 @@ +class Dep { + // The API representation for `bla`'s result type contains a cycle + // (an existential's type variable's bound is the existential type itself) + // This results in a stack overflow while showing the API diff. + // Note that the actual result type in the compiler is not cyclic + // (the f-bounded existential for Comparable is truncated) + def bla(c: Boolean) = if (c) new Value else "bla" +} + +class Value extends java.lang.Comparable[Value] { def compareTo(that: Value): Int = 1 } \ No newline at end of file diff --git a/sbt/src/sbt-test/source-dependencies/fbounded-existentials/test b/sbt/src/sbt-test/source-dependencies/fbounded-existentials/test new file mode 100644 index 000000000..5df2af1f3 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/fbounded-existentials/test @@ -0,0 +1 @@ +> compile