aboutsummaryrefslogtreecommitdiff
path: root/compiler/src/dotty/tools/dotc/sbt/ThunkHolder.scala
blob: abdd5cfdd5342b21176e51285170aaf1db0308c1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package dotty.tools
package dotc
package sbt

import scala.annotation.tailrec
import scala.collection.mutable.ListBuffer
import xsbti.api

/** Create and hold thunks. A thunk is a (potentially) unevaluated value
 *  that may be evaluated once.
 */
private[sbt] trait ThunkHolder {
  private[this] val thunks = new ListBuffer[api.Lazy[_]]

  /** Force all unevaluated thunks to prevent space leaks. */
  @tailrec protected final def forceThunks(): Unit = if (!thunks.isEmpty) {
    val toForce = thunks.toList
    thunks.clear()
    toForce.foreach(_.get())
    // Forcing thunks may create new thunks
    forceThunks()
  }

  /** Store the by-name parameter `s` in a `Lazy` container without evaluating it.
   *  It will be forced by the next call to `forceThunks()`
   */
  def lzy[T <: AnyRef](t: => T): api.Lazy[T] = {
    val l = SafeLazyWrapper(() => t)
    thunks += l
    l
  }

  /** Store the parameter `s` in a `Lazy` container, since `s` is not by-name, there
   *  is nothing to force.
   *
   *  TODO: Get rid of this method. It is only needed because some xsbti.api classes
   *  take lazy arguments when they could be strict, but this can be fixed in sbt,
   *  see https://github.com/sbt/zinc/issues/114
   */
  def strict2lzy[T <: AnyRef](t: T): api.Lazy[T] =
    SafeLazyWrapper.strict(t)
}

/** Wrapper around SafeLazy implementations.
 *
 *  `xsbti.SafeLazy` is part of sbt but it is not part of the `interface` jar
 *  that dotty depends on, therefore we can only access it by reflection,
 *  and this will only succeed when dotty is run by sbt (otherwise
 *  `xsbti.SafeLazy` won't be on the classpath at all).
 *
 *  For testing purposes, we still want to be able to run the sbt phases outside
 *  of sbt, using `-Yforce-sbt-phases` and `-Ydump-sbt-inc`, therefore we
 *  provide a copy of SafeLazy in `dotty.tools.dotc.sbt.SafeLazy` that we use
 *  when `xsbti.SafeLazy` is unavailable.
 *
 *  This raises a question: why bother with `xsbti.SafeLazy` if we have our own
 *  version anyway? Because sbt uses Java serialization to persist the output of
 *  the incremental compilation analysis when sbt is stopped and restarted. If
 *  we used `dotty.tools.dotc.sbt.SafeLazy` with sbt, deserialization would fail
 *  and every restart of sbt would require a full recompilation.
 *
 *  Note: this won't be needed once we switch to zinc 1.0 where `SafeLazy` becomes
 *  part of the `interface` jar, see https://github.com/sbt/zinc/issues/113
 */
private object SafeLazyWrapper {

  @sharable private[this] val safeLazy =
    try {
      Class.forName("xsbti.SafeLazy")
    } catch {
      case e: ClassNotFoundException =>
        null
    }

  @sharable private[this] val safeLazyApply =
    if (safeLazy != null)
      safeLazy.getMethod("apply", classOf[xsbti.F0[_]])
    else
      null
  @sharable private[this] val safeLazyStrict =
    if (safeLazy != null)
      safeLazy.getMethod("strict", classOf[Object])
    else
      null

  def apply[T <: AnyRef](eval: () => T): xsbti.api.Lazy[T] =
    if (safeLazyApply != null)
      safeLazyApply
        .invoke(null, new xsbti.F0[T] { def apply() = eval() })
        .asInstanceOf[xsbti.api.Lazy[T]]
    else
      SafeLazy(eval)

  def strict[T <: AnyRef](value: T): xsbti.api.Lazy[T] =
    if (safeLazyStrict != null)
      safeLazyStrict
        .invoke(null, value)
        .asInstanceOf[xsbti.api.Lazy[T]]
    else
      SafeLazy.strict(value)
}

// Adapted from https://github.com/sbt/sbt/blob/0.13/compile/api/src/main/scala/xsbti/SafeLazy.scala
private object SafeLazy {
  def apply[T <: AnyRef](eval: () => T): xsbti.api.Lazy[T] =
    new Impl(eval)

  def strict[T <: AnyRef](value: T): xsbti.api.Lazy[T] =
    new Strict(value)

  private[this] final class Impl[T <: AnyRef](private[this] var eval: () => T) extends xsbti.api.AbstractLazy[T] {
    private[this] lazy val _t = {
      val t = eval()
      eval = null // clear the reference, ensuring the only memory we hold onto is the result
      t
    }
    def get: T = _t
  }

  private[this] final class Strict[T <: AnyRef](val get: T) extends xsbti.api.Lazy[T] with java.io.Serializable
}