From 214451c51f37f7759afed236cc81a0db339afc8f Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Mon, 26 Oct 2015 10:30:26 -0700 Subject: [PATCH 01/12] API extraction ensures class symbol is initialized Call `initialize` in case symbol's `info` hadn't been completed during normal compilation. Also, normalize to the class symbol immediately. Add a TODO regarding only looking at class symbols, and thus ignoring the term symbol for objects, as the corresponding class symbol has all the relevant info. --- .../src/main/scala/xsbt/ExtractAPI.scala | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/compile/interface/src/main/scala/xsbt/ExtractAPI.scala b/compile/interface/src/main/scala/xsbt/ExtractAPI.scala index 2d94f5d3b..945626e12 100644 --- a/compile/interface/src/main/scala/xsbt/ExtractAPI.scala +++ b/compile/interface/src/main/scala/xsbt/ExtractAPI.scala @@ -530,20 +530,25 @@ class ExtractAPI[GlobalType <: CallbackGlobal](val global: GlobalType, private def selfType(in: Symbol, s: Symbol): xsbti.api.Type = processType(in, s.thisSym.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(structure(in, 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 From 1ce11230546639fd4a06ab96d19784979a7a358c Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Mon, 26 Oct 2015 10:48:39 -0700 Subject: [PATCH 02/12] API only tracks declared type of self variable The only aspect of the self variable that's relevant for incremental compilation is its explicitly declared type, and only when it's different from the type of the class that declares it. Technically, any self type that's a super type of the class could be ignored, as it cannot affect external use (instantiation/subclassing) of the class. --- .../interface/src/main/scala/xsbt/ExtractAPI.scala | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/compile/interface/src/main/scala/xsbt/ExtractAPI.scala b/compile/interface/src/main/scala/xsbt/ExtractAPI.scala index 945626e12..b410f7198 100644 --- a/compile/interface/src/main/scala/xsbt/ExtractAPI.scala +++ b/compile/interface/src/main/scala/xsbt/ExtractAPI.scala @@ -527,7 +527,16 @@ class ExtractAPI[GlobalType <: CallbackGlobal](val global: GlobalType, 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 = { From 51524055f795bb333520af75c93c217c8666eb80 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 1 Dec 2015 14:54:47 -0800 Subject: [PATCH 03/12] Reduce memory usage of ExtractDependenciesTraverser --- .../interface/src/main/scala/xsbt/Dependency.scala | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/compile/interface/src/main/scala/xsbt/Dependency.scala b/compile/interface/src/main/scala/xsbt/Dependency.scala index a72f615a6..89e02d6d3 100644 --- a/compile/interface/src/main/scala/xsbt/Dependency.scala +++ b/compile/interface/src/main/scala/xsbt/Dependency.scala @@ -95,12 +95,9 @@ 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 val deps = collection.mutable.HashSet.empty[Symbol] + protected def addDependency(dep: Symbol): Unit = if (dep ne NoSymbol) deps += dep + def dependencies: Iterator[Symbol] = deps.iterator } private class ExtractDependenciesByMemberRefTraverser extends ExtractDependenciesTraverser { @@ -158,7 +155,7 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile { } } - private def extractDependenciesByMemberRef(unit: CompilationUnit): collection.immutable.Set[Symbol] = { + private def extractDependenciesByMemberRef(unit: CompilationUnit): Iterator[Symbol] = { val traverser = new ExtractDependenciesByMemberRefTraverser traverser.traverse(unit.body) val dependencies = traverser.dependencies @@ -184,7 +181,7 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile { } } - private def extractDependenciesByInheritance(unit: CompilationUnit): collection.immutable.Set[Symbol] = { + private def extractDependenciesByInheritance(unit: CompilationUnit): Iterator[Symbol] = { val traverser = new ExtractDependenciesByInheritanceTraverser traverser.traverse(unit.body) val dependencies = traverser.dependencies From 3e03e0d1b597172cdb3a8893f11a23ef20661417 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 1 Dec 2015 23:02:25 -0800 Subject: [PATCH 04/12] Clean up ShowApi implicit overload Motivated because we want to make it more robust & configurable. Original motivation was to diagnose a cyclic type representation, likely due to an f-bounded existential type, as illustrated by the following: ``` 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 } ``` Limit nesting (`-Dsbt.inc.apidiff.depth=N`, where N defaults to `2`), and number of declarations shown for a class/structural type (via `sbt.inc.apidiff.decls`, which defaults to `0` -- no limit). Limiting nesting is crucial in keeping the size of api diffs of large programs within a reasonable amount of RAM... For example, compiling the Scala library, the API diff with nesting at `4` exhausts 4G of RAM... --- .../api/src/main/scala/xsbt/api/ShowAPI.scala | 405 ++++++------------ .../inc/src/main/scala/sbt/inc/APIDiff.scala | 7 +- .../src/main/scala/xsbt/ExtractAPI.scala | 7 +- .../api/show-circular-structure/build.sbt | 5 +- 4 files changed, 142 insertions(+), 282 deletions(-) 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/interface/src/main/scala/xsbt/ExtractAPI.scala b/compile/interface/src/main/scala/xsbt/ExtractAPI.scala index b410f7198..9179c2c87 100644 --- a/compile/interface/src/main/scala/xsbt/ExtractAPI.scala +++ b/compile/interface/src/main/scala/xsbt/ExtractAPI.scala @@ -515,9 +515,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,7 +525,7 @@ class ExtractAPI[GlobalType <: CallbackGlobal](val global: GlobalType, case None => s.fullName } - } + /* 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. */ 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) } From 9346e2bb4da36945706bd7ee145f3950d7936944 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 5 Jan 2016 12:50:03 -0800 Subject: [PATCH 05/12] Only include all base types for class definitions For refinement types, the Structure was already restricted to declarations (and not inherited members), but all base types were still included for a refinement's parents, which would create unwieldy, and even erroneous (cyclic) types by expanding all constituents of an intersection type to add all base types. Since the logic already disregarded inherited members, it seems logical to only include direct parents, and not all ancestor types. ``` class Dep { def bla(c: Boolean) = if (c) new Value else "bla" } class Value extends java.lang.Comparable[Value] { def compareTo(that: Value): Int = 1 } ``` --- .../src/main/scala/xsbt/ExtractAPI.scala | 21 ++++++++++++------- .../fbounded-existentials/fbounds.scala | 10 +++++++++ .../fbounded-existentials/test | 1 + 3 files changed, 24 insertions(+), 8 deletions(-) create mode 100644 sbt/src/sbt-test/source-dependencies/fbounded-existentials/fbounds.scala create mode 100644 sbt/src/sbt-test/source-dependencies/fbounded-existentials/test diff --git a/compile/interface/src/main/scala/xsbt/ExtractAPI.scala b/compile/interface/src/main/scala/xsbt/ExtractAPI.scala index 9179c2c87..4575dc27e 100644 --- a/compile/interface/src/main/scala/xsbt/ExtractAPI.scala +++ b/compile/interface/src/main/scala/xsbt/ExtractAPI.scala @@ -336,14 +336,19 @@ class ExtractAPI[GlobalType <: CallbackGlobal](val global: GlobalType, 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) - } + private def mkStructure(info: Type, s: Symbol, inherit: Boolean): xsbti.api.Structure = { + val (declared, inherited) = info.members.reverse.partition(_.owner == s) + // Note that the ordering of classes in `baseClasses` is important. + // It would be easier to just say `val baseTypes = baseTypeSeq`, but that does not seem + // to take linearization into account. + // Also, we take info.parents when we're not interested in the full linearization, + // 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) + val baseTypes = if (inherit) info.baseClasses.tail.map(info.baseType) else info.parents + val ds = if (s.isModuleClass) removeConstructors(declared) else declared + val is = if (inherit) removeConstructors(inherited) else Nil + mkStructure(s, baseTypes, ds, is) + } // 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. 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 From 8c73b2f221e9f0088383d575b8f045943cde3e32 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Wed, 2 Dec 2015 23:43:57 -0800 Subject: [PATCH 06/12] Refactor mkStructure Specialize two implementations for each value of the `inherit` boolean argument. Also use a more direct way of distinguishing declared and inherited members. backwards compat for source-dependencies/inherited-dependencies --- .../src/main/scala/xsbt/ExtractAPI.scala | 62 +++++++++++++------ 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/compile/interface/src/main/scala/xsbt/ExtractAPI.scala b/compile/interface/src/main/scala/xsbt/ExtractAPI.scala index 4575dc27e..6875f5d40 100644 --- a/compile/interface/src/main/scala/xsbt/ExtractAPI.scala +++ b/compile/interface/src/main/scala/xsbt/ExtractAPI.scala @@ -329,27 +329,51 @@ 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.reverse.partition(_.owner == s) - // Note that the ordering of classes in `baseClasses` is important. - // It would be easier to just say `val baseTypes = baseTypeSeq`, but that does not seem - // to take linearization into account. - // Also, we take info.parents when we're not interested in the full linearization, - // 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) - val baseTypes = if (inherit) info.baseClasses.tail.map(info.baseType) else info.parents - 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. private[this] def isPublicStructure(s: Symbol): Boolean = @@ -473,7 +497,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) @@ -486,7 +510,7 @@ class ExtractAPI[GlobalType <: CallbackGlobal](val global: GlobalType, 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 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)) @@ -556,7 +580,7 @@ class ExtractAPI[GlobalType <: CallbackGlobal](val global: GlobalType, } else DefinitionType.ClassDef new xsbti.api.ClassLike( - defType, lzy(selfType(in, sym)), lzy(structure(in, sym)), emptyStringArray, typeParameters(in, sym), // look at class symbol + 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 } From 0f616294c4e713dc415f5dc3ae7aef257decb228 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 11 Dec 2015 11:51:32 -0800 Subject: [PATCH 07/12] Extract dependencies in one pass. Also a bit more complete: handle SelectFromTypeTree, consider the self type an inheritance dependency, and flatten any refinement types in inherited types, to get to the symbols of their parents, instead of the useless symbol of the refinement class. Include inheritance dependencies in regular ones Also, update test to reflect the self type is now seen as an inheritance dependency. self types are local, so don't treat them like inherited types note inheritanceSymbols dealiases, where allSymbols is constructed differently fix NPE in source-dependencies/macro-annotation --- .../src/main/scala/xsbt/Dependency.scala | 202 +++++++++--------- .../scala/xsbt/DependencySpecification.scala | 6 +- 2 files changed, 100 insertions(+), 108 deletions(-) diff --git a/compile/interface/src/main/scala/xsbt/Dependency.scala b/compile/interface/src/main/scala/xsbt/Dependency.scala index 89e02d6d3..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,106 +177,15 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile { } } - private abstract class ExtractDependenciesTraverser extends Traverser { - private val deps = collection.mutable.HashSet.empty[Symbol] - protected def addDependency(dep: Symbol): Unit = if (dep ne NoSymbol) deps += dep - def dependencies: Iterator[Symbol] = deps.iterator - } - - 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): Iterator[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): Iterator[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/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 From d32a0eaaa06780e089683b9c9be2def32930796b Mon Sep 17 00:00:00 2001 From: Krzysztof Romanowski Date: Wed, 2 Sep 2015 21:43:30 +0200 Subject: [PATCH 08/12] Exclude all non static annotations from ExtractAPI --- compile/interface/src/main/scala/xsbt/ExtractAPI.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compile/interface/src/main/scala/xsbt/ExtractAPI.scala b/compile/interface/src/main/scala/xsbt/ExtractAPI.scala index 6875f5d40..5c4475cd3 100644 --- a/compile/interface/src/main/scala/xsbt/ExtractAPI.scala +++ b/compile/interface/src/main/scala/xsbt/ExtractAPI.scala @@ -636,7 +636,7 @@ class ExtractAPI[GlobalType <: CallbackGlobal](val global: GlobalType, // 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; + associated.flatMap(ss => annotations(in, ss.annotations.filter(_.isStatic))).distinct.toArray; } private def annotatedType(in: Symbol, at: AnnotatedType): xsbti.api.Type = { From 698902ba44daa2f223b687ce3ce5cd44db72db34 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 15 Dec 2015 16:08:20 -0800 Subject: [PATCH 09/12] API limited to static annotations to mimic pickling Since pickled annotated types and symbols only mention static annotations, whereas compilation from source sees all annotations, we must explicitly filter annotations in the API representation using the same criteria as the pickler, so that we generate the same API when compiling from source as when we're loading classfiles. --- .../src/main/scala/xsbt/ExtractAPI.scala | 56 +++++++++++-------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/compile/interface/src/main/scala/xsbt/ExtractAPI.scala b/compile/interface/src/main/scala/xsbt/ExtractAPI.scala index 5c4475cd3..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) + " )") @@ -509,7 +523,11 @@ 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 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 @@ -628,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.filter(_.isStatic))).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) + } } From 81786a220698efa9289bdad68d94f14ee6ccbc8e Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 15 Dec 2015 23:47:57 -0800 Subject: [PATCH 10/12] Prefix name hashing debug output with [naha] Further categorize debug output as api diff ([diff]), and invalidation ([inv]). Since we log a ton of diagnostics, make it a bit easier to find the relevant bits. --- compile/inc/src/main/scala/sbt/inc/Incremental.scala | 8 +++++++- .../src/main/scala/sbt/inc/IncrementalCommon.scala | 11 ++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) 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.") From 5661b0fac4f4b3d0730b7d30ccee6ec0564f07fd Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 6 Jan 2016 17:52:53 -0500 Subject: [PATCH 11/12] Adds release note to #2343 --- notes/0.13.10.markdown | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/notes/0.13.10.markdown b/notes/0.13.10.markdown index 666d9f9cc..3ab68c916 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 the tracking of self types in the incremental compiler. [#2343][2343] by [@adriaanm][@adriaanm] +- Fixes the tracking of F-bounded existential types in the incremental compiler. [#2343][2343] by [@adriaanm][@adriaanm] ### Def.settings From 83b725559f42299b7cc339e84e8eed01aeeeebd6 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 6 Jan 2016 22:12:01 -0500 Subject: [PATCH 12/12] Fix notes --- notes/0.13.10.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/notes/0.13.10.markdown b/notes/0.13.10.markdown index 3ab68c916..c787503cd 100644 --- a/notes/0.13.10.markdown +++ b/notes/0.13.10.markdown @@ -126,8 +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 the tracking of self types in the incremental compiler. [#2343][2343] by [@adriaanm][@adriaanm] -- Fixes the tracking of F-bounded existential types in the incremental compiler. [#2343][2343] by [@adriaanm][@adriaanm] +- 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