Skip to content

Rework type inference #4080

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 19 commits into from
Mar 7, 2018
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
5 changes: 5 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,11 @@ object Trees {
s"TypeTree${if (hasType) s"[$typeOpt]" else ""}"
}

/** A type tree that defines a new type variable. Its type is always a TypeVar.
* Every TypeVar is created as the type of one TypeVarBinder.
*/
class TypeVarBinder[-T >: Untyped] extends TypeTree[T]

/** ref.type */
case class SingletonTypeTree[-T >: Untyped] private[ast] (ref: Tree[T])
extends DenotingTree[T] with TypTree[T] {
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ class ScalaSettings extends Settings.SettingGroup {
val YdisableFlatCpCaching = BooleanSetting("-YdisableFlatCpCaching", "Do not cache flat classpath representation of classpath elements from jars across compiler instances.")

val YnoImports = BooleanSetting("-Yno-imports", "Compile without importing scala.*, java.lang.*, or Predef.")
val YnoInline = BooleanSetting("-Yno-inline", "Suppress inlining.")
val YnoGenericSig = BooleanSetting("-Yno-generic-signatures", "Suppress generation of generic signatures for Java.")
val YnoPredef = BooleanSetting("-Yno-predef", "Compile without importing Predef.")
val Yskip = PhasesSetting("-Yskip", "Skip")
Expand Down Expand Up @@ -126,7 +127,7 @@ class ScalaSettings extends Settings.SettingGroup {
val YexplainLowlevel = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.")
val YnoDoubleBindings = BooleanSetting("-Yno-double-bindings", "Assert no namedtype is bound twice (should be enabled only if program is error-free).")
val YshowVarBounds = BooleanSetting("-Yshow-var-bounds", "Print type variables with their bounds")
val YnoInline = BooleanSetting("-Yno-inline", "Suppress inlining.")
val YshowNoInline = BooleanSetting("-Yshow-no-inline", "Show inlined code without the 'inlined from' info")

/** Linker specific flags */
val optimise = BooleanSetting("-optimise", "Generates faster bytecode by applying local optimisations to the .program") withAbbreviation "-optimize"
Expand Down
15 changes: 10 additions & 5 deletions compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala
Original file line number Diff line number Diff line change
Expand Up @@ -86,19 +86,21 @@ trait ConstraintHandling {
finally homogenizeArgs = saved
}

private def location(implicit ctx: Context) = "" // i"in ${ctx.typerState.stateChainStr}" // use for debugging

protected def addUpperBound(param: TypeParamRef, bound: Type): Boolean = {
def description = i"constraint $param <: $bound to\n$constraint"
if (bound.isRef(defn.NothingClass) && ctx.typerState.isGlobalCommittable) {
def msg = s"!!! instantiated to Nothing: $param, constraint = ${constraint.show}"
if (Config.failOnInstantiationToNothing) assert(false, msg)
else ctx.log(msg)
}
constr.println(i"adding $description in ${ctx.typerState.hashesStr}")
constr.println(i"adding $description$location")
val lower = constraint.lower(param)
val res =
addOneBound(param, bound, isUpper = true) &&
lower.forall(addOneBound(_, bound, isUpper = true))
constr.println(i"added $description = $res in ${ctx.typerState.hashesStr}")
constr.println(i"added $description = $res$location")
res
}

Expand All @@ -109,7 +111,7 @@ trait ConstraintHandling {
val res =
addOneBound(param, bound, isUpper = false) &&
upper.forall(addOneBound(_, bound, isUpper = false))
constr.println(i"added $description = $res in ${ctx.typerState.hashesStr}")
constr.println(i"added $description = $res$location")
res
}

Expand All @@ -122,12 +124,12 @@ trait ConstraintHandling {
val up2 = p2 :: constraint.exclusiveUpper(p2, p1)
val lo1 = constraint.nonParamBounds(p1).lo
val hi2 = constraint.nonParamBounds(p2).hi
constr.println(i"adding $description down1 = $down1, up2 = $up2 ${ctx.typerState.hashesStr}")
constr.println(i"adding $description down1 = $down1, up2 = $up2$location")
constraint = constraint.addLess(p1, p2)
down1.forall(addOneBound(_, hi2, isUpper = true)) &&
up2.forall(addOneBound(_, lo1, isUpper = false))
}
constr.println(i"added $description = $res ${ctx.typerState.hashesStr}")
constr.println(i"added $description = $res$location")
res
}

Expand Down Expand Up @@ -252,6 +254,9 @@ trait ConstraintHandling {
case tp: SingletonType => true
case AndType(tp1, tp2) => isMultiSingleton(tp1) | isMultiSingleton(tp2)
case OrType(tp1, tp2) => isMultiSingleton(tp1) & isMultiSingleton(tp2)
case tp: TypeRef => isMultiSingleton(tp.info.hiBound)
case tp: TypeVar => isMultiSingleton(tp.underlying)
case tp: TypeParamRef => isMultiSingleton(bounds(tp).hi)
case _ => false
}
def isFullyDefined(tp: Type): Boolean = tp match {
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Denotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ object Denotations {
if ((cachedPrefix ne pre) || ctx.period != validAsSeenFrom) {
cachedAsSeenFrom = computeAsSeenFrom(pre)
cachedPrefix = pre
validAsSeenFrom = ctx.period
validAsSeenFrom = if (pre.isProvisional) Nowhere else ctx.period
}
cachedAsSeenFrom
} else computeAsSeenFrom(pre)
Expand Down
26 changes: 24 additions & 2 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1191,6 +1191,26 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
isSubRef(tp1, tp2) && isSubRef(tp2, tp1)
}

/** If the range `tp1..tp2` consist of a single type, that type, otherwise NoType`.
* This is the case if `tp1 =:= tp2`, but also if `tp1 <:< tp2`, `tp1` is a singleton type,
* and `tp2` derives from `scala.Singleton` (or vice-versa). Examples of the latter case:
*
* "name".type .. Singleton
* "name".type .. String & Singleton
* Singleton .. "name".type
* String & Singleton .. "name".type
*
* All consist of the single type `"name".type`.
*/
def singletonInterval(tp1: Type, tp2: Type): Type = {
def isSingletonBounds(lo: Type, hi: Type) =
lo.isSingleton && hi.derivesFrom(defn.SingletonClass) && isSubTypeWhenFrozen(lo, hi)
if (isSameTypeWhenFrozen(tp1, tp2)) tp1
else if (isSingletonBounds(tp1, tp2)) tp1
else if (isSingletonBounds(tp2, tp1)) tp2
else NoType
}

/** The greatest lower bound of two types */
def glb(tp1: Type, tp2: Type): Type = /*>|>*/ trace(s"glb(${tp1.show}, ${tp2.show})", subtyping, show = true) /*<|<*/ {
if (tp1 eq tp2) tp1
Expand Down Expand Up @@ -1279,9 +1299,10 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
case tparam :: tparamsRest =>
val arg1 :: args1Rest = args1
val arg2 :: args2Rest = args2
val common = singletonInterval(arg1, arg2)
val v = tparam.paramVariance
val lubArg =
if (isSameTypeWhenFrozen(arg1, arg2)) arg1
if (common.exists) common
else if (v > 0) lub(arg1.hiBound, arg2.hiBound, canConstrain)
else if (v < 0) glb(arg1.loBound, arg2.loBound)
else TypeBounds(glb(arg1.loBound, arg2.loBound),
Expand Down Expand Up @@ -1310,9 +1331,10 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
case tparam :: tparamsRest =>
val arg1 :: args1Rest = args1
val arg2 :: args2Rest = args2
val common = singletonInterval(arg1, arg2)
val v = tparam.paramVariance
val glbArg =
if (isSameTypeWhenFrozen(arg1, arg2)) arg1
if (common.exists) common
else if (v > 0) glb(arg1.hiBound, arg2.hiBound)
else if (v < 0) lub(arg1.loBound, arg2.loBound)
else if (arg1.isInstanceOf[TypeBounds] || arg2.isInstanceOf[TypeBounds])
Expand Down
37 changes: 18 additions & 19 deletions compiler/src/dotty/tools/dotc/core/TyperState.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,23 @@ package core
import Types._
import Flags._
import Contexts._
import util.{SimpleIdentityMap, DotClass}
import util.{SimpleIdentityMap, SimpleIdentitySet, DotClass}
import reporting._
import printing.{Showable, Printer}
import printing.Texts._
import config.Config
import collection.mutable
import java.lang.ref.WeakReference
import Decorators._

class TyperState(previous: TyperState /* | Null */) extends DotClass with Showable {
object TyperState {
@sharable private var nextId: Int = 0
}

class TyperState(previous: TyperState /* | Null */) {

val id = TyperState.nextId
TyperState.nextId += 1

private[this] var myReporter =
if (previous == null) new ConsoleReporter() else previous.reporter
Expand Down Expand Up @@ -42,19 +50,6 @@ class TyperState(previous: TyperState /* | Null */) extends DotClass with Showab
private val previousConstraint =
if (previous == null) constraint else previous.constraint

private[this] 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
* 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 = myEphemeral
def ephemeral_=(x: Boolean): Unit = { myEphemeral = x }

private[this] var myIsCommittable = true

def isCommittable = myIsCommittable
Expand All @@ -81,6 +76,11 @@ class TyperState(previous: TyperState /* | Null */) extends DotClass with Showab
/** The uninstantiated variables */
def uninstVars = constraint.uninstVars

/** The set of uninstantiated type variables which have this state as their owning state */
private[this] var myOwnedVars: TypeVars = SimpleIdentitySet.empty
def ownedVars = myOwnedVars
def ownedVars_=(vs: TypeVars): Unit = myOwnedVars = vs

/** 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
Expand Down Expand Up @@ -159,7 +159,7 @@ class TyperState(previous: TyperState /* | Null */) extends DotClass with Showab
constraint foreachTypeVar { tvar =>
if (tvar.owningState.get eq this) tvar.owningState = new WeakReference(targetState)
}
targetState.ephemeral |= ephemeral
targetState.ownedVars ++= ownedVars
targetState.gc()
reporter.flush()
isCommitted = true
Expand All @@ -185,8 +185,7 @@ class TyperState(previous: TyperState /* | Null */) extends DotClass with Showab
constraint = constraint.remove(poly)
}

override def toText(printer: Printer): Text = constraint.toText(printer)
override def toString: String = s"TS[$id]"

def hashesStr: String =
if (previous == null) "" else hashCode.toString + " -> " + previous.hashesStr
def stateChainStr: String = s"$this${if (previous == null) "" else previous.stateChainStr}"
}
Loading