aboutsummaryrefslogtreecommitdiff
path: root/compiler/src/dotty/tools/dotc/core/TyperState.scala
blob: b33b3aa2955307a0f0dd9f70ad9a9a7fc5b15d35 (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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
package dotty.tools
package dotc
package core

import Types._
import Flags._
import Contexts._
import util.{SimpleMap, DotClass}
import reporting._
import printing.{Showable, Printer}
import printing.Texts._
import config.Config
import collection.mutable

class TyperState(r: Reporter) extends DotClass with Showable {

  /** The current reporter */
  def reporter = r

  /** The current constraint set */
  def constraint: Constraint =
    new OrderingConstraint(SimpleMap.Empty, SimpleMap.Empty, SimpleMap.Empty)
  def constraint_=(c: Constraint)(implicit ctx: Context): Unit = {}

  /** The uninstantiated variables */
  def uninstVars = constraint.uninstVars

  /** The ephemeral flag is set as a side effect if an operation accesses
   *  the underlying type of a type variable. The reason we need this flag is
   *  that any such operation is not referentially transparent; it might logically change
   *  its value at the moment the type variable is instantiated. Caching code needs to
   *  check the ephemeral flag; If the flag is set during an operation, the result
   *  of that operation should not be cached.
   */
  def ephemeral: Boolean = false
  def ephemeral_=(x: Boolean): Unit = ()

  /** Gives for each instantiated type var that does not yet have its `inst` field
   *  set, the instance value stored in the constraint. Storing instances in constraints
   *  is done only in a temporary way for contexts that may be retracted
   *  without also retracting the type var as a whole.
   */
  def instType(tvar: TypeVar)(implicit ctx: Context): Type = constraint.entry(tvar.origin) match {
    case _: TypeBounds => NoType
    case tp: TypeParamRef =>
      var tvar1 = constraint.typeVarOfParam(tp)
      if (tvar1.exists) tvar1 else tp
    case tp => tp
  }

  /** A fresh typer state with the same constraint as this one.
   *  @param isCommittable  The constraint can be committed to an enclosing context.
   */
  def fresh(isCommittable: Boolean): TyperState = this

  /** A fresh type state with the same constraint as this one and the given reporter */
  def withReporter(reporter: Reporter) = new TyperState(reporter)

  /** Commit state so that it gets propagated to enclosing context */
  def commit()(implicit ctx: Context): Unit = unsupported("commit")

  /** The closest ancestor of this typer state (including possibly this typer state itself)
   *  which is not yet committed, or which does not have a parent.
   */
  def uncommittedAncestor: TyperState = this

  /** Make type variable instances permanent by assigning to `inst` field if
   *  type variable instantiation cannot be retracted anymore. Then, remove
   *  no-longer needed constraint entries.
   */
  def gc()(implicit ctx: Context): Unit = ()

  /** Is it allowed to commit this state? */
  def isCommittable: Boolean = false

  /** Can this state be transitively committed until the top-level? */
  def isGlobalCommittable: Boolean = false

  def tryWithFallback[T](op: => T)(fallback: => T)(implicit ctx: Context): T = unsupported("tryWithFallBack")

  override def toText(printer: Printer): Text = "ImmutableTyperState"

  /** A string showing the hashes of all nested mutable typerstates */
  def hashesStr: String = ""
}

class MutableTyperState(previous: TyperState, r: Reporter, override val isCommittable: Boolean)
extends TyperState(r) {

  private var myReporter = r

  override def reporter = myReporter

  private val previousConstraint = previous.constraint
  private var myConstraint: Constraint = previousConstraint

  override def constraint = myConstraint
  override def constraint_=(c: Constraint)(implicit ctx: Context) = {
    if (Config.debugCheckConstraintsClosed && isGlobalCommittable) c.checkClosed()
    myConstraint = c
  }

  private var myEphemeral: Boolean = previous.ephemeral

  override def ephemeral = myEphemeral
  override def ephemeral_=(x: Boolean): Unit = { myEphemeral = x }

  override def fresh(isCommittable: Boolean): TyperState =
    new MutableTyperState(this, new StoreReporter(reporter), isCommittable)

  override def withReporter(reporter: Reporter) =
    new MutableTyperState(this, reporter, isCommittable)

  override val isGlobalCommittable =
    isCommittable &&
    (!previous.isInstanceOf[MutableTyperState] || previous.isGlobalCommittable)

  private var isCommitted = false

  override def uncommittedAncestor: TyperState =
    if (isCommitted) previous.uncommittedAncestor else this

  /** Commit typer state so that its information is copied into current typer state
   *  In addition (1) the owning state of undetermined or temporarily instantiated
   *  type variables changes from this typer state to the current one. (2) Variables
   *  that were temporarily instantiated in the current typer state are permanently
   *  instantiated instead.
   *
   *  A note on merging: An interesting test case is isApplicableSafe.scala. It turns out that this
   *  requires a context merge using the new `&' operator. Sequence of actions:
   *  1) Typecheck argument in typerstate 1.
   *  2) Cache argument.
   *  3) Evolve same typer state (to typecheck other arguments, say)
   *     leading to a different constraint.
   *  4) Take typechecked argument in same state.
   *
   * It turns out that the merge is needed not just for
   * isApplicableSafe but also for (e.g. erased-lubs.scala) as well as
   * many parts of dotty itself.
   */
  override def commit()(implicit ctx: Context) = {
    val targetState = ctx.typerState
    assert(isCommittable)
    targetState.constraint =
      if (targetState.constraint eq previousConstraint) constraint
      else targetState.constraint & constraint
    constraint foreachTypeVar { tvar =>
      if (tvar.owningState eq this)
        tvar.owningState = targetState
    }
    targetState.ephemeral |= ephemeral
    targetState.gc()
    reporter.flush()
    isCommitted = true
  }

  override def gc()(implicit ctx: Context): Unit = {
    val toCollect = new mutable.ListBuffer[TypeLambda]
    constraint foreachTypeVar { tvar =>
      if (!tvar.inst.exists) {
        val inst = instType(tvar)
        if (inst.exists && (tvar.owningState eq this)) {
          tvar.inst = inst
          val lam = tvar.origin.binder
          if (constraint.isRemovable(lam)) toCollect += lam
        }
      }
    }
    for (poly <- toCollect)
      constraint = constraint.remove(poly)
  }

  /** Try operation `op`; if it produces errors, execute `fallback` with constraint and
   *  reporter as they were before `op` was executed. This is similar to `typer/tryEither`,
   *  but with one important difference: Any type variable instantiations produced by `op`
   *  are persisted even if `op` fails. This is normally not what one wants and therefore
   *  it is recommended to use
   *
   *      tryEither { implicit ctx => op } { (_, _) => fallBack }
   *
   *  instead of
   *
   *      ctx.tryWithFallback(op)(fallBack)
   *
   *  `tryWithFallback` is only used when an implicit parameter search fails
   *  and the whole expression is subsequently retype-checked with a Wildcard
   *  expected type (so as to allow an implicit conversion on the result and
   *  avoid over-constraining the implicit parameter search). In this case,
   *  the only type variables that might be falsely instantiated by `op` but
   *  not by `fallBack` are type variables in the typed expression itself, and
   *  these will be thrown away and new ones will be created on re-typing.
   *  So `tryWithFallback` is safe. It is also necessary because without it
   *  we do not propagate enough instantiation information into the implicit search
   *  and this might lead to a missing parameter type error. This is exhibited
   *  at several places in the test suite (for instance in `pos_typers`).
   *  Overall, this is rather ugly, but despite trying for 2 days I have not
   *  found a better solution.
   */
  override def tryWithFallback[T](op: => T)(fallback: => T)(implicit ctx: Context): T = {
    val storeReporter = new StoreReporter(myReporter)
    val savedReporter = myReporter
    myReporter = storeReporter
    val savedConstraint = myConstraint
    val result = try op finally myReporter = savedReporter
    if (!storeReporter.hasErrors) result
    else {
      myConstraint = savedConstraint
      fallback
    }
  }

  override def toText(printer: Printer): Text = constraint.toText(printer)

  override def hashesStr: String = hashCode.toString + " -> " + previous.hashesStr

}