|
1 | 1 | package mill.api.internal |
2 | 2 |
|
| 3 | +import collection.mutable.LinkedHashSet |
3 | 4 | import scala.collection.mutable |
| 5 | +import scala.util.DynamicVariable |
4 | 6 | import scala.quoted.* |
5 | 7 |
|
6 | 8 | trait Cacher extends mill.moduledefs.Cacher { |
7 | 9 | private lazy val cacherLazyMap = mutable.Map.empty[sourcecode.Enclosing, Any] |
8 | 10 |
|
9 | 11 | protected def cachedTask[T](t: => T)(using c: sourcecode.Enclosing): T = synchronized { |
10 | | - cacherLazyMap.getOrElseUpdate(c, t).asInstanceOf[T] |
| 12 | + if (Cacher.taskEvaluationStack.value.contains((c, this))) { |
| 13 | + sys.error( |
| 14 | + "Circular task dependency detected:\n" + |
| 15 | + (Cacher.taskEvaluationStack.value.toList ++ Seq((c, this))) |
| 16 | + .map { case (c, o) => |
| 17 | + val taskName = c.value.split("\\.|#| ").filter(!_.startsWith("$anon")).last |
| 18 | + o.toString match { |
| 19 | + case "" => taskName |
| 20 | + case s => s + "." + taskName |
| 21 | + } |
| 22 | + } |
| 23 | + .mkString("\ndepends on: ") |
| 24 | + ) |
| 25 | + } |
| 26 | + |
| 27 | + try { |
| 28 | + Cacher.taskEvaluationStack.value.add((c, this)) |
| 29 | + cacherLazyMap.getOrElseUpdate(c, t).asInstanceOf[T] |
| 30 | + } finally { |
| 31 | + Cacher.taskEvaluationStack.value.remove((c, this)) |
| 32 | + } |
11 | 33 | } |
12 | 34 | } |
13 | 35 |
|
14 | 36 | private[mill] object Cacher { |
| 37 | + // Use a LinkedHashSet for fast contains checking while preserving insertion order |
| 38 | + private[mill] val taskEvaluationStack = |
| 39 | + DynamicVariable[LinkedHashSet[(sourcecode.Enclosing, Any)]](LinkedHashSet()) |
15 | 40 | private[mill] def withMacroOwner[T](using Quotes)(op: quotes.reflect.Symbol => T): T = { |
16 | 41 | import quotes.reflect.* |
17 | 42 | // In Scala 3, the top level splice of a macro is owned by a symbol called "macro" with the macro flag set, |
|
0 commit comments