Skip to content

Avoid exploring TyperState creations #3186

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/Run.scala
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class Run(comp: Compiler, ictx: Context) {
.setOwner(defn.RootClass)
.setTyper(new Typer)
.addMode(Mode.ImplicitsEnabled)
.setTyperState(new MutableTyperState(ctx.typerState, ctx.typerState.reporter, isCommittable = true))
.setTyperState(new TyperState(ctx.typerState))
.setFreshNames(new FreshNameCreator.Default)
ctx.initialize()(start) // re-initialize the base context with start
def addImport(ctx: Context, refFn: () => TermRef) =
Expand Down
8 changes: 4 additions & 4 deletions compiler/src/dotty/tools/dotc/core/Contexts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -459,9 +459,9 @@ object Contexts {
def setCompilerCallback(callback: CompilerCallback): this.type = { this.compilerCallback = callback; this }
def setSbtCallback(callback: AnalysisCallback): this.type = { this.sbtCallback = callback; this }
def setTyperState(typerState: TyperState): this.type = { this.typerState = typerState; this }
def setReporter(reporter: Reporter): this.type = setTyperState(typerState.withReporter(reporter))
def setNewTyperState: this.type = setTyperState(typerState.fresh(isCommittable = true))
def setExploreTyperState: this.type = setTyperState(typerState.fresh(isCommittable = false))
def setReporter(reporter: Reporter): this.type = setTyperState(typerState.fresh().setReporter(reporter))
def setNewTyperState(): this.type = setTyperState(typerState.fresh().setCommittable(true))
def setExploreTyperState(): this.type = setTyperState(typerState.fresh().setCommittable(false))
def setPrinterFn(printer: Context => Printer): this.type = { this.printerFn = printer; this }
def setOwner(owner: Symbol): this.type = { assert(owner != NoSymbol); this.owner = owner; this }
def setSettings(sstate: SettingsState): this.type = { this.sstate = sstate; this }
Expand Down Expand Up @@ -520,7 +520,7 @@ object Contexts {
outer = NoContext
period = InitialPeriod
mode = Mode.None
typerState = new TyperState(new ConsoleReporter())
typerState = new TyperState(null)
printerFn = new RefinedPrinter(_)
owner = NoSymbol
sstate = settings.defaultState
Expand Down
152 changes: 73 additions & 79 deletions compiler/src/dotty/tools/dotc/core/TyperState.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,31 @@ import config.Config
import collection.mutable
import java.lang.ref.WeakReference

class TyperState(r: Reporter) extends DotClass with Showable {
class TyperState(previous: TyperState /* | Null */) extends DotClass with Showable {

/** The current reporter */
def reporter = r
private var myReporter =
if (previous == null) new ConsoleReporter() else previous.reporter

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

/** The uninstantiated variables */
def uninstVars = constraint.uninstVars
/** A fresh type state with the same constraint as this one and the given reporter */
def setReporter(reporter: Reporter): this.type = { myReporter = reporter; this }

private var myConstraint: Constraint =
if (previous == null) new OrderingConstraint(SimpleMap.Empty, SimpleMap.Empty, SimpleMap.Empty)
else previous.constraint

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

private val previousConstraint =
if (previous == null) constraint else previous.constraint

private var myEphemeral: Boolean =
if (previous == null) false else previous.ephemeral

/** 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
Expand All @@ -33,8 +46,26 @@ class TyperState(r: Reporter) extends DotClass with Showable {
* 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 = ()
def ephemeral = myEphemeral
def ephemeral_=(x: Boolean): Unit = { myEphemeral = x }

private var myIsCommittable = true

def isCommittable = myIsCommittable

def setCommittable(committable: Boolean): this.type = { this.myIsCommittable = committable; this }

def isGlobalCommittable: Boolean =
isCommittable && (previous == null || previous.isGlobalCommittable)

private var isCommitted = false

/** A fresh typer state with the same constraint as this one. */
def fresh(): TyperState =
new TyperState(this).setReporter(new StoreReporter(reporter)).setCommittable(isCommittable)

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

/** 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
Expand All @@ -49,76 +80,36 @@ class TyperState(r: Reporter) extends DotClass with Showable {
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

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
def uncommittedAncestor: TyperState =
if (isCommitted) previous.uncommittedAncestor else this

override def constraint = myConstraint
override def constraint_=(c: Constraint)(implicit ctx: Context) = {
if (Config.debugCheckConstraintsClosed && isGlobalCommittable) c.checkClosed()
myConstraint = c
private var testReporter: StoreReporter = null

/** Test using `op`, restoring typerState to previous state afterwards */
def test(op: => Boolean): Boolean = {
val savedReporter = myReporter
val savedConstraint = myConstraint
val savedCommittable = myIsCommittable
val savedCommitted = isCommitted
myIsCommittable = false
myReporter =
if (testReporter == null) new StoreReporter(reporter)
else {
testReporter.reset()
testReporter
}
try op
finally {
myReporter = savedReporter
myConstraint = savedConstraint
myIsCommittable = savedCommittable
isCommitted = savedCommitted
}
}

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
Expand All @@ -137,7 +128,7 @@ extends TyperState(r) {
* isApplicableSafe but also for (e.g. erased-lubs.scala) as well as
* many parts of dotty itself.
*/
override def commit()(implicit ctx: Context) = {
def commit()(implicit ctx: Context) = {
val targetState = ctx.typerState
assert(isCommittable)
targetState.constraint =
Expand All @@ -152,7 +143,11 @@ extends TyperState(r) {
isCommitted = true
}

override def gc()(implicit ctx: Context): Unit = {
/** 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 = {
val toCollect = new mutable.ListBuffer[TypeLambda]
constraint foreachTypeVar { tvar =>
if (!tvar.inst.exists) {
Expand All @@ -170,6 +165,5 @@ extends TyperState(r) {

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

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

def hashesStr: String = hashCode.toString + " -> " + previous.hashesStr
}
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ class StoreReporter(outer: Reporter) extends Reporter {

private var infos: mutable.ListBuffer[MessageContainer] = null

def reset() = infos = null

def doReport(m: MessageContainer)(implicit ctx: Context): Unit = {
typr.println(s">>>> StoredError: ${m.message}") // !!! DEBUG
if (infos == null) infos = new mutable.ListBuffer
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/patmat/Space.scala
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {

val childTp = if (child.isTerm) child.termRef else child.typeRef

val resTp = instantiate(childTp, parent)(ctx.fresh.setNewTyperState)
val resTp = instantiate(childTp, parent)(ctx.fresh.setNewTyperState())

if (!resTp.exists) {
debug.println(s"[refine] unqualified child ousted: ${childTp.show} !< ${parent.show}")
Expand Down
57 changes: 22 additions & 35 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -847,7 +847,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
def followTypeAlias(tree: untpd.Tree): untpd.Tree = {
tree match {
case tree: untpd.RefTree =>
val nestedCtx = ctx.fresh.setNewTyperState
val nestedCtx = ctx.fresh.setNewTyperState()
val ttree =
typedType(untpd.rename(tree, tree.name.toTypeName))(nestedCtx)
ttree.tpe match {
Expand Down Expand Up @@ -1002,26 +1002,20 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
/** Is given method reference applicable to type arguments `targs` and argument trees `args`?
* @param resultType The expected result type of the application
*/
def isApplicable(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context): Boolean = {
val nestedContext = ctx.fresh.setExploreTyperState
new ApplicableToTrees(methRef, targs, args, resultType)(nestedContext).success
}
def isApplicable(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context): Boolean =
ctx.typerState.test(new ApplicableToTrees(methRef, targs, args, resultType).success)

/** Is given method reference applicable to type arguments `targs` and argument trees `args` without inferring views?
* @param resultType The expected result type of the application
*/
def isDirectlyApplicable(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context): Boolean = {
val nestedContext = ctx.fresh.setExploreTyperState
new ApplicableToTreesDirectly(methRef, targs, args, resultType)(nestedContext).success
}
def isDirectlyApplicable(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context): Boolean =
ctx.typerState.test(new ApplicableToTreesDirectly(methRef, targs, args, resultType).success)

/** Is given method reference applicable to argument types `args`?
* @param resultType The expected result type of the application
*/
def isApplicable(methRef: TermRef, args: List[Type], resultType: Type)(implicit ctx: Context): Boolean = {
val nestedContext = ctx.fresh.setExploreTyperState
new ApplicableToTypes(methRef, args, resultType)(nestedContext).success
}
def isApplicable(methRef: TermRef, args: List[Type], resultType: Type)(implicit ctx: Context): Boolean =
ctx.typerState.test(new ApplicableToTypes(methRef, args, resultType).success)

/** Is given type applicable to type arguments `targs` and argument trees `args`,
* possibly after inserting an `apply`?
Expand Down Expand Up @@ -1102,12 +1096,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
case tp2: MethodType => true // (3a)
case tp2: PolyType if tp2.resultType.isInstanceOf[MethodType] => true // (3a)
case tp2: PolyType => // (3b)
val nestedCtx = ctx.fresh.setExploreTyperState

{
implicit val ctx = nestedCtx
isAsSpecificValueType(tp1, constrained(tp2).resultType)
}
ctx.typerState.test(isAsSpecificValueType(tp1, constrained(tp2).resultType))
case _ => // (3b)
isAsSpecificValueType(tp1, tp2)
}
Expand Down Expand Up @@ -1257,22 +1246,20 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
* probability of pruning the search. result type comparisons are neither cheap nor
* do they prune much, on average.
*/
def adaptByResult(chosen: TermRef) = {
def nestedCtx = ctx.fresh.setExploreTyperState
pt match {
case pt: FunProto if !resultConforms(chosen, pt.resultType)(nestedCtx) =>
alts.filter(alt =>
(alt ne chosen) && resultConforms(alt, pt.resultType)(nestedCtx)) match {
case Nil => chosen
case alt2 :: Nil => alt2
case alts2 =>
resolveOverloaded(alts2, pt) match {
case alt2 :: Nil => alt2
case _ => chosen
}
}
case _ => chosen
}
def adaptByResult(chosen: TermRef) = pt match {
case pt: FunProto if !ctx.typerState.test(resultConforms(chosen, pt.resultType)) =>
val conformingAlts = alts.filter(alt =>
(alt ne chosen) && ctx.typerState.test(resultConforms(alt, pt.resultType)))
conformingAlts match {
case Nil => chosen
case alt2 :: Nil => alt2
case alts2 =>
resolveOverloaded(alts2, pt) match {
case alt2 :: Nil => alt2
case _ => chosen
}
}
case _ => chosen
}

var found = resolveOverloaded(alts, pt, Nil)(ctx.retractMode(Mode.ImplicitsEnabled))
Expand Down
Loading