romanowski.kr@gmail.com
Scala compilation is
*compared to Java
What incremental compiler is?
Incremental compiler is a driver for scalac that for given change recompile only subset of sources that will produce the same output as full compilation.
Incremental compilers for Scala?
explained in scala code
All scala examples below are pseudocode.
Actual sbt code is too complicated and hairy to be shown on such presentation.
def compile(compilation: Compilation): Unit = {
    var changed = changedSources(compilation)
    while(changed.nonEmpty){
        val oldAPIs = changed.map(currentAPI)
        val newAPIs = doCompile(changed)
        val changedAPIs = diff(oldAPIs, newAPIs)
        changed = changedAPIs.flatMap(allDependencies)
    }
}
Do we need recursion?
// Team.scala
class Team {
    def room = "1" // change to 1
}
// User.scala
class User(team: Team) {
    val room: String = team.room // this must fail
}
def compile(compilation: Compilation): Unit = {
    var changed = changedSources(compilation)
    while(changed.nonEmpty){
        val oldAPIs = changed.map(currentAPI)
        val newAPIs = doCompile(changed)
        val changedAPIs = diff(oldAPIs, newAPIs)
        changed = changedAPIs.flatMap(allDependencies)
    }
}
def changedSources(compilation: Compilation): Seq[File] = {
    if(outputChanged || optionsChanged) // Then compile all
        compilation.allSources
    else // Compile only changed files
        compilation.allSources.filter(changedSource)
}
private def doCompile(source: Seq[File]): Seq[API] = {
    val listener = new SourceListener
    runScalac(listener)
    updateDependencies(listener.dependencies)
    listener.apis
}
class SourceListener{
    def process(unit: CompilationUnit): Unit = {
    // Compute hashes
    val unitTraverser = new HashingTraverser.run(unit.tree)
    _apis +=  API(unit.source, unitTraverser.unitHash)
    // Compute dependencies
    val depsTraverser = new DepsTraverser.run(unit.tree)
    _dependencies += (unit.source -> depsTraverser.dependencies)
    }
}
trait TreeTraverser {
    def traverse(tree: Tree): Unit
    def run(tree: Tree): this.type
}
class HashingTraverser extends TreeTraverser {
    var unitHash = EmptyHash
    override def traverse(tree: Tree): Unit = tree match {
        case ClassTree(sig, members) if !sig.isPrivate =>
            unitHash.hash(sig)
            members.foreach(sig)
        case Method(sig, body) if !sig.isPrivate =>
            unitHash.hash(sig)
        //  ...
class DepsTraverser extends TreeTraverser {
    var dependecies: Set[File] = Set.empty
    override def traverse(tree: Tree): Unit = tree match {
        case ClassTree(signature, members) =>
            dependecies += signature.declaredIn
            members.foreach(traverse)
        case  Method(signature, body) =>
            dependecies += signature.declaredIn
            traverse(body)
        case Operation(tpe, _) =>
            dependecies += tpe.declaredIn
        // ...
def compile(compilation: Compilation): Unit = {
    var changed = changedSources(compilation)
    while(changed.nonEmpty){
        val oldAPIs = changed.map(currentAPI)
        val newAPIs = doCompile(changed)
        val changedAPIs = diff(oldAPIs, newAPIs)
        changed = changedAPIs.flatMap(allDependencies)
    }
}
Explained as diff on scala code
case class API(source: File, hash: Hash) // becomes case class NamedAPI(source: File, hashes: Map[String,Hash])
var dependecies: Set[File] = Set.empty becomes var fileDependencies: Set[File] = Set.empty var nameDependencies: Set[String] = Set.empty
def isAffected(changedAPI: Seq[File, Set[String],
               source: File): Boolean = {
    changedAPI.find {
        case (changedSource, changedNames) =>
            def useChangedNames =
                (usedNames(source) & changedNames).isDefined
            isDependency(source, changedSource) && useChangedNames
    }
}
val it = Seq("Adam", "Marcin")
val hr = Seq("Ola", "Ala")
class TeamMember(name: String, team: String)
def allTeamMembers: Seq[TeamMember] = ???...
//This is Seq[Seq[String]]
def teams = Seq(it, hr)
//This is Iterable[Seq[String]]
def teams = allTeamMembers.groupBy(_.team).map {
    case (_, usersInTeam) =>
        usersInTeam.map(_.name)
}
romanowski.kr@gmail.com
Class (file) is changed when
It's source file changed It's not-private API changedtrait Dep class Generic[E[V] <: Seq[V with Dep]] // yields same javap output as class Generic[E[V] <: Seq[V]]
public class romanowski.changes.Generic
    <E extends scala.collection.Seq<java.lang.Object>> {
    public romanowski.changes.Generic();
    Code:
    0: aload_0
    1: invokespecial #13  // Method java/lang/Object."<init>":()V
    4: return
}
// Implicits.scala
class Implicits(i: Int) {
    implicit class FooBarOps(from: FooImpl[_]){
        def fooBar = 1
    }
}
// FooImpl.scala
class FooImpl[A] extends Foo[A]
// Baz.scala
class Baz {
    val foo = new FooImpl[Bar]
    foo.fooBar
}
// Foo.scala class Foo[A] object Foo // extends Implicits(1) // Uncomment this // Bar.scala class Bar object Bar extends Implicits(1)
There is no direct path that leads from changes in Foo$ (companion object from Foo) to Baz.
There is no direct path that leads from changes in Foo$ (companion object from Foo) to Baz.
Do we want to reimplement compiler?
romanowski.kr@gmail.com