Skip to content

Default ClassfileManager doesn't recover from partially successful compilation results #958

@gkossakowski

Description

@gkossakowski

Let's assume that incOptions := sbt.inc.IncOptions.Default which means we are using default compiler options. Let's start with these sources:

// A.scala
class A {
  def initialized: Boolean = false
}

// B.scala
class B {
  def foo(a: A): Boolean = a.initialized
}

and compile them. Then let's comment out initialized method:

// A.scala
class A {
//  def initialized: Boolean = false
}

and try to compile. The incremental compiler will compile A.scala first, then it will try to recompile B.scala and it will fail. The error will get reported as expected. Now, if we change A.scala back to its original content:

// A.scala
class A {
  def initialized: Boolean = false
}

and we try to compile again we expect it should succeed. However, it will report the same error as with second compile attempt.

Let's see what happened exactly. After commenting out initialized in A.scala incremental compiler invoked Scala compiler that compiled A.scala and produced a new class file for A. It failed to compile B.scala so class file for B got removed. However, since the whole incremental compilation session failed the Analysis object rolled back so it has a hash of original A.scala file.

Now, once we changed A.scala back to original file and we asked to recompile again. Since Analysis object contains the hash of original file the incremental compiler doesn't notice the change to A.scala. However, since class file for B.scala has been removed (and that wasn't recorded) it tries to recompile B.scala and uses stale class file for A. Here's the relevant log output:

> compile
[debug] 
[debug] Initial source changes: 
[debug]     removed:Set()
[debug]     added: Set()
[debug]     modified: Set()
[debug] Removed products: Set(/Users/grek/scala/xsbt/sbt/src/sbt-test/compiler-project/error-in-invalidated/target/scala-2.10/classes/B.class)

The solution to this problem is to make ClassfileManager transactional so it commits changes to class files only when entire session of incremental compilation succeeds. This way Analysis object and class files stay in sync.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions