diff --git a/internal/zinc-core/src/main/scala/sbt/internal/inc/IncrementalCommon.scala b/internal/zinc-core/src/main/scala/sbt/internal/inc/IncrementalCommon.scala index 35df2eae8..e88bada1f 100644 --- a/internal/zinc-core/src/main/scala/sbt/internal/inc/IncrementalCommon.scala +++ b/internal/zinc-core/src/main/scala/sbt/internal/inc/IncrementalCommon.scala @@ -441,7 +441,7 @@ private[inc] abstract class IncrementalCommon( /** * Invalidates classes internally to a project after an incremental compiler run. * - * @param relations The relations produced by the immediate previous incremental compiler cycle. + * @param analysis The analysis produced by the immediate previous incremental compiler cycle. * @param changes The changes produced by the immediate previous incremental compiler cycle. * @param recompiledClasses The immediately recompiled class names. * @param invalidateTransitively A flag that tells whether transitive invalidations should be @@ -478,7 +478,20 @@ private[inc] abstract class IncrementalCommon( if (secondClassInvalidation.nonEmpty) log.debug(s"Invalidated due to generated class file collision: ${secondClassInvalidation}") - // Invalidate macros that transitively depend on any of the recompiled classes + // Invalidate macro classes that transitively depend on any of the recompiled classes + // + // The macro expansion tree can depend on the behavioural change of any upstream code change, + // not just API changes, so correctness requires aggressive recompilation of downstream classes. + // + // Technically the macro doesn't need to be recompiled - it's the classes downstream of the macro + // that need to be recompiled, so that the macros can be re-expanded. But recompiling is the most + // straightforward way to signal any classes downstream of the _macro_ that they need to recompile. + // + // Also, note, that this solution only works for behavioural changes in sources within the same + // subproject as the macro. Changes in behaviour in upstream subprojects don't cause downstream + // macro classes to recompile - because downstream projects only have visibility of the upstream + // API, and if it changed, which is insufficient, and upstream projects have no other way than + // their API to signal to downstream. val thirdClassInvalidation = { val transitive = IncrementalCommon.transitiveDeps(recompiledClasses, log)(dependsOnClass) (transitive -- recompiledClasses).filter(analysis.apis.internalAPI(_).hasMacro)