diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index 943b54d7f9a8..c5c1d8713342 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -45,8 +45,10 @@ class Compiler { List(new RefChecks, new ElimRepeated, new ElimLocals, - new ExtensionMethods, - new TailRec), + new ExtensionMethods), + List(new TailRec), // TailRec needs to be in its own group for now. + // Otherwise it produces -Ycheck incorrect code for + // file core/Decorators.scala. List(new PatternMatcher, new ExplicitOuter, new Splitter), diff --git a/src/dotty/tools/dotc/Run.scala b/src/dotty/tools/dotc/Run.scala index f7d89e897f91..abee30aab69c 100644 --- a/src/dotty/tools/dotc/Run.scala +++ b/src/dotty/tools/dotc/Run.scala @@ -46,6 +46,7 @@ class Run(comp: Compiler)(implicit ctx: Context) { .filterNot(ctx.settings.Yskip.value.containsPhase(_)) // TODO: skip only subphase for (phase <- phasesToRun) if (!ctx.reporter.hasErrors) { + if (ctx.settings.verbose.value) println(s"[$phase]") units = phase.runOn(units) def foreachUnit(op: Context => Unit)(implicit ctx: Context): Unit = for (unit <- units) op(ctx.fresh.setPhase(phase.next).setCompilationUnit(unit)) diff --git a/src/dotty/tools/dotc/ast/Desugar.scala b/src/dotty/tools/dotc/ast/Desugar.scala index 32ada504daa0..7cdedb19a6cb 100644 --- a/src/dotty/tools/dotc/ast/Desugar.scala +++ b/src/dotty/tools/dotc/ast/Desugar.scala @@ -59,7 +59,6 @@ object desugar { case tp: NamedType if tp.symbol.owner eq originalOwner => val defctx = ctx.outersIterator.dropWhile(_.scope eq ctx.scope).next var local = defctx.denotNamed(tp.name).suchThat(_ is ParamOrAccessor).symbol - typr.println(s"rewiring ${tp.symbol} from ${originalOwner.showLocated} to ${local.showLocated}, current owner = ${ctx.owner.showLocated}") if (local.exists) (defctx.owner.thisType select local).dealias else throw new Error(s"no matching symbol for ${sym.showLocated} in ${defctx.owner} / ${defctx.effectiveScope}") case _ => diff --git a/src/dotty/tools/dotc/config/Config.scala b/src/dotty/tools/dotc/config/Config.scala index b17a5630f98a..fe4e888291cd 100644 --- a/src/dotty/tools/dotc/config/Config.scala +++ b/src/dotty/tools/dotc/config/Config.scala @@ -10,8 +10,33 @@ object Config { final val checkCacheMembersNamed = false - final val checkConstraintsNonCyclic = true - + /** When updating a connstraint bound, check that the constrained parameter + * does not appear at the top-level of either of its bounds. + */ + final val checkConstraintsNonCyclic = false + + /** Like `checkConstraintsNonCyclic`, but all constrained parameters + * are tested for direct or indirect dependencies, each time a + * constraint is added in TypeComparer. + */ + final val checkConstraintsNonCyclicTrans = false + + /** Check that each constraint resulting from a subtype test + * is satisfiable. + */ + final val checkConstraintsSatisfiable = false + + /** Check that each constraint is fully propagated. i.e. + * If P <: Q then the upper bound of P is a subtype of the upper bound of Q + * and the lower bound of Q is a subtype of the lower bound of P. + */ + final val checkConstraintsPropagated = false + + /** Type comparer will fail with an assert if the upper bound + * of a constrained parameter becomes Nothing. This should be turned + * on only for specific debugging as normally instantiation to Nothing + * is not an error consdition. + */ final val flagInstantiationToNothing = false /** Enable noDoubleDef checking if option "-YnoDoubleDefs" is set. @@ -24,7 +49,11 @@ object Config { /** Show subtype traces for all deep subtype recursions */ final val traceDeepSubTypeRecursions = false + /** When explaining subtypes and this flag is set, also show the classes of the compared types. */ final val verboseExplainSubtype = true + + /** If this flag is set, take the fast path when comparing same-named type-aliases and types */ + final val fastPathForRefinedSubtype = true /** When set, use new signature-based matching. * Advantantage of doing so: It's supposed to be faster @@ -35,12 +64,6 @@ object Config { /** The recursion depth for showing a summarized string */ final val summarizeDepth = 2 - /** Track dependencies for constraint propagation satisfiability checking - * If turned off, constraint checking is simpler but potentially slower - * for large constraints. - */ - final val trackConstrDeps = true - /** Check that variances of lambda arguments match the * variance of the underlying lambda class. */ diff --git a/src/dotty/tools/dotc/config/ScalaSettings.scala b/src/dotty/tools/dotc/config/ScalaSettings.scala index 35c3ac16ada4..281a4b6c751f 100644 --- a/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -167,7 +167,7 @@ class ScalaSettings extends Settings.SettingGroup { val Yrepldebug = BooleanSetting("-Yrepl-debug", "Trace all repl activity.") val Ytyperdebug = BooleanSetting("-Ytyper-debug", "Trace all type assignments.") val Ypatmatdebug = BooleanSetting("-Ypatmat-debug", "Trace pattern matching translation.") - val Yexplainlowlevel = BooleanSetting("-Yexplainlowlevel", "When explaining type errors, show types at a lower level.") + val Yexplainlowlevel = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.") val YnoDoubleBindings = BooleanSetting("-YnoDoubleBindings", "Assert no namedtype is bound twice (should be enabled only if program is error-free).") val optimise = BooleanSetting("-optimise", "Generates faster bytecode by applying optimisations to the program") withAbbreviation "-optimize" diff --git a/src/dotty/tools/dotc/core/Constants.scala b/src/dotty/tools/dotc/core/Constants.scala index 48c832c4bf3b..61a23bb9e33e 100644 --- a/src/dotty/tools/dotc/core/Constants.scala +++ b/src/dotty/tools/dotc/core/Constants.scala @@ -169,7 +169,7 @@ object Constants { def convertTo(pt: Type)(implicit ctx: Context): Constant = { def lowerBound(pt: Type): Type = pt.dealias.stripTypeVar match { case tref: TypeRef if !tref.symbol.isClass => lowerBound(tref.info.bounds.lo) - case param: PolyParam => lowerBound(ctx.typeComparer.bounds(param).lo) + case param: PolyParam => lowerBound(ctx.typerState.constraint.nonParamBounds(param).lo) case pt => pt } val target = lowerBound(pt).typeSymbol diff --git a/src/dotty/tools/dotc/core/Constraint.scala b/src/dotty/tools/dotc/core/Constraint.scala index e241794d0e4f..d6e1589e0eca 100644 --- a/src/dotty/tools/dotc/core/Constraint.scala +++ b/src/dotty/tools/dotc/core/Constraint.scala @@ -10,451 +10,138 @@ import printing.Texts._ import config.Config import config.Printers._ -object Constraint { - - /** The type of `Constraint#myMap` */ - type ParamInfo = SimpleMap[PolyType, Array[Type]] - - /** The type of `Constraint#dependents */ - type DependentMap = SimpleMap[PolyType, Array[Set[PolyParam]]] - - /** The type of functions that include or exclude a `PolyParam` in or from a set*/ - private type DepDelta = (Set[PolyParam], PolyParam) => Set[PolyParam] - - private val addDep: DepDelta = (_ + _) - private val removeDep: DepDelta = (_ - _) - - private val NoTypeBounds = new TypeBounds(WildcardType, WildcardType) { - override def computeHash = unsupported("computeHash") - } - - /** An accumulator that changes dependencies on `param`. - * @param param The parameter to which changed dependencies refer. - * @param ofVariance Include `PolyParams` occurring at this variance in the dependencies. - * @param delta The dependency change to perform (add or remove). - */ - private class ChangeDependencies(param: PolyParam, ofVariance: Int, delta: DepDelta)(implicit ctx: Context) - extends TypeAccumulator[DependentMap] { - def apply(deps: DependentMap, tp: Type): DependentMap = tp match { - case tp @ PolyParam(pt, n) if - this.variance == 0 || this.variance == ofVariance => - val oldDeps = deps(pt) - val original = safeSelect(oldDeps, n) - val changed = delta(original, param) - if (original eq changed) deps - else { - val newDeps = - if (oldDeps == null) new Array[Set[PolyParam]](pt.paramBounds.length) - else oldDeps.clone - newDeps(n) = changed - deps.updated(pt, newDeps) - } - case _ => foldOver(deps, tp) - } - } - - /** `deps(n)`, except that `Set()` is returned if `deps` or `deps(n)` are null */ - private def safeSelect(deps: Array[Set[PolyParam]], n: Int) : Set[PolyParam] = - if (deps == null || deps(n) == null) Set() - else deps(n) -} - -import Constraint._ - -/** Constraint over undetermined type parameters - * @param myMap a map from PolyType to arrays. - * Each array contains twice the number of entries as there a type parameters - * in the PolyType. The first half of the array contains the type bounds that constrain the - * polytype's type parameters. The second half might contain type variables that - * track the corresponding parameters, or is left empty (filled with nulls). - * An instantiated type parameter is represented by having its instance type in - * the corresponding array entry. - * @param dependents a map from PolyTypes to arrays of Sets of PolyParams. - * The i'th set in an array corresponding to polytype `pt` contains - * those dependent `PolyParam`s `dp` that have `PolyParam(pt, i)` in their bounds in - * significant position. A position is significant if solving the - * constraint for `(pt, i)` with a type higher than its lower bound - * would lead to a constraint for `dp` that was not looser than - * the existing constraint. Specifically, it means that all poly params - * appearing covariantly in the lower bound and contravariantly in the - * upper bound, as well as all poly params appearing nonvariantly are - * significant. - * The `dependents` map is maintained and queried only of `Config.trackConstrDeps` is set. +/** Constraint over undetermined type parameters. Constraints are built + * over values of the following types: + * + * - PolyType A constraint constrains the type parameters of a set of PolyTypes + * - PolyParam The parameters of the constrained polytypes + * - TypeVar Every constrained parameter might be associated with a TypeVar + * that has the PolyParam as origin. */ -class Constraint(private val myMap: ParamInfo, - private val dependents: DependentMap) extends Showable { - +abstract class Constraint extends Showable { + + type This <: Constraint + /** Does the constraint's domain contain the type parameters of `pt`? */ - def contains(pt: PolyType): Boolean = myMap(pt) != null + def contains(pt: PolyType): Boolean /** Does the constraint's domain contain the type parameter `param`? */ - def contains(param: PolyParam): Boolean = { - val entries = myMap(param.binder) - entries != null && entries(param.paramNum).isInstanceOf[TypeBounds] - } + def contains(param: PolyParam): Boolean /** Does this constraint contain the type variable `tvar` and is it uninstantiated? */ - def contains(tvar: TypeVar): Boolean = { - val origin = tvar.origin - val entries = myMap(origin.binder) - val pnum = origin.paramNum - entries != null && isBounds(entries(pnum)) && (typeVar(entries, pnum) eq tvar) - } - - /** The number of type parameters in the given entry array */ - private def paramCount(entries: Array[Type]) = entries.length >> 1 - - /** The type variable corresponding to parameter numbered `n`, null if none was created */ - private def typeVar(entries: Array[Type], n: Int): Type = - entries(paramCount(entries) + n) - - private def isBounds(tp: Type) = tp.isInstanceOf[TypeBounds] - - /** The constraint for given type parameter `param`, or NoType if `param` is not part of + def contains(tvar: TypeVar): Boolean + + /** The constraint entry for given type parameter `param`, or NoType if `param` is not part of * the constraint domain. */ - def at(param: PolyParam): Type = { - val entries = myMap(param.binder) - if (entries == null) NoType else entries(param.paramNum) - } - - /** The constraint bounds for given type parameter `param`. - * @pre `param` is not part of the constraint domain. - */ - def bounds(param: PolyParam): TypeBounds = at(param).asInstanceOf[TypeBounds] - + def entry(param: PolyParam): Type + /** The type variable corresponding to parameter `param`, or * NoType, if `param` is not in constrained or is not paired with a type variable. */ - def typeVarOfParam(param: PolyParam): Type = { - val entries = myMap(param.binder) - if (entries == null) NoType - else { - val tvar = typeVar(entries, param.paramNum) - if (tvar != null) tvar else NoType - } - } + def typeVarOfParam(param: PolyParam): Type + + /** Is it known that `param1 <:< param2`? */ + def isLess(param1: PolyParam, param2: PolyParam): Boolean + + /** The parameters that are known to be smaller wrt <: than `param` */ + def lower(param: PolyParam): List[PolyParam] + + /** The parameters that are known to be greater wrt <: than `param` */ + def upper(param: PolyParam): List[PolyParam] + + /** lower(param) \ lower(butNot) */ + def exclusiveLower(param: PolyParam, butNot: PolyParam): List[PolyParam] + + /** upper(param) \ upper(butNot) */ + def exclusiveUpper(param: PolyParam, butNot: PolyParam): List[PolyParam] - /** Change dependencies in map `deps` to reflect new parameter bounds. - * @param deps The map to change - * @param pt the polytype that contains the parameters which might have new bounds - * @param entries the entries for the parameters which might have new bounds - * @param delta the change operation, one of `addDep` or `removeDep`. - * @param cmpEntries the comparison entries or `null` if no such entries exist. - * As an optimization, only bounds that differ between `entries` - * and `cmpEntries` will record their dependencies. + /** The constraint bounds for given type parameter `param`. + * Poly params that are known to be smaller or greater than `param` + * are not contained in the return bounds. + * @pre `param` is not part of the constraint domain. */ - def changeDependencies(deps: DependentMap, pt: PolyType, entries: Array[Type], delta: DepDelta, cmpEntries: Array[Type])(implicit ctx: Context): DependentMap = { - val limit = paramCount(entries) - def loop(deps: DependentMap, n: Int): DependentMap = { - if (n >= limit) deps - else { - val newDeps = entries(n) match { - case bounds @ TypeBounds(lo, hi) => - val cmpBounds = - if (cmpEntries == null) NoTypeBounds - else cmpEntries(n) match { - case bounds: TypeBounds => bounds - case _ => NoTypeBounds - } - if (cmpBounds eq bounds) deps - else { - val param = PolyParam(pt, n) - val deps1 = - if (cmpBounds.lo eq lo) deps - else new ChangeDependencies(param, 1, delta).apply(deps, lo) - val deps2 = - if (cmpBounds.hi eq hi) deps1 - else new ChangeDependencies(param, -1, delta).apply(deps1, hi) - deps2 - } - case _ => - deps - } - loop(newDeps, n + 1) - } - } - if (Config.trackConstrDeps) loop(deps, 0) else deps - } - - /** Change dependencies to reflect all changes between the bounds in `oldMap` and `newMap`. + def nonParamBounds(param: PolyParam): TypeBounds + + /** The lower bound of `param` including all known-to-be-smaller parameters */ + def fullLowerBound(param: PolyParam)(implicit ctx: Context): Type + + /** The upper bound of `param` including all known-to-be-greater parameters */ + def fullUpperBound(param: PolyParam)(implicit ctx: Context): Type + + /** The bounds of `param` including all known-to-be-smaller and -greater parameters */ + def fullBounds(param: PolyParam)(implicit ctx: Context): TypeBounds + + /** A new constraint which is derived from this constraint by adding + * entries for all type parameters of `poly`. + * @param tvars A list of type variables associated with the params, + * or Nil if the constraint will just be checked for + * satisfiability but will solved to give instances of + * type variables. */ - def diffDependencies(deps: DependentMap, oldMap: ParamInfo, newMap: ParamInfo)(implicit ctx: Context): DependentMap = - if (Config.trackConstrDeps) { - var d = deps - oldMap foreachBinding { (poly, entries) => - val newEntries = newMap(poly) - if (newEntries ne entries) d = changeDependencies(d, poly, entries, removeDep, newEntries) - } - newMap foreachBinding { (poly, entries) => - val oldEntries = oldMap(poly) - if (oldEntries ne entries) d = changeDependencies(d, poly, entries, addDep, oldEntries) - } - d - } else deps + def add(poly: PolyType, tvars: List[TypeVar])(implicit ctx: Context): This - /** The set of parameters that depend directly on `param` - * according to what's stored in `dependents`. + /** A new constraint which is derived from this constraint by updating + * the entry for parameter `param` to `tp`. + * `tp` can be one of the following: + * + * - A TypeBounds value, indicating new constraint bounds + * - Another type, indicating a solution for the parameter + * + * @pre `this contains param`. */ - def dependentParams(param: PolyParam): Set[PolyParam] = - safeSelect(dependents(param.binder), param.paramNum) - - /** A new constraint which is derived from this constraint by adding or replacing - * the entries corresponding to `pt` with `entries`. + def updateEntry(param: PolyParam, tp: Type)(implicit ctx: Context): This + + /** A constraint that includes the relationship `p1 <: p2` */ - private def updateEntries(pt: PolyType, entries: Array[Type])(implicit ctx: Context) : Constraint = { - val res = new Constraint( - myMap.updated(pt, entries), - changeDependencies(dependents, pt, entries, addDep, myMap(pt))) - - //assert(res.domainPolys.filter(pt => - // pt.resultType.resultType.widen.classSymbol.name.toString == "Ensuring").length < 2) //DEBUG - if (Config.checkConstraintsNonCyclic) checkNonCyclic(pt, entries) - ctx.runInfo.recordConstraintSize(res, res.myMap.size) - res - } - - /** Check that no constrained parameter in `pt` contains itself as a bound */ - def checkNonCyclic(pt: PolyType, entries: Array[Type])(implicit ctx: Context): Unit = - for ((entry, i) <- entries.zipWithIndex) { - val param = PolyParam(pt, i) - entry match { - case TypeBounds(lo, hi) => - assert(!param.occursIn(lo, fromBelow = true), s"$param occurs below $lo") - assert(!param.occursIn(hi, fromBelow = false), s"$param occurs above $hi") - case _ => - } - } - - /** Check that no constrained parameter contains itself as a bound */ - def checkNonCyclic()(implicit ctx: Context): Unit = { - for (pt <- domainPolys) checkNonCyclic(pt, myMap(pt)) - } + def addLess(p1: PolyParam, p2: PolyParam)(implicit ctx: Context): This - /** A new constraint which is derived from this constraint by updating - * the entry for parameter `param` to `tpe`. - * @pre `this contains param`. + /** A constraint resulting by adding p2 = p1 to this constraint, and at the same + * time transferring all bounds of p2 to p1 */ - def updated(param: PolyParam, tpe: Type)(implicit ctx: Context): Constraint = { - val newEntries = myMap(param.binder).clone - newEntries(param.paramNum) = tpe - updateEntries(param.binder, newEntries) - } + def unify(p1: PolyParam, p2: PolyParam)(implicit ctx: Context): This - /** A new constraint which is derived from this constraint by mapping - * `op` over all entries of `poly`. - * @pre `this contains poly`. + /** A new constraint which is derived from this constraint by removing + * the type parameter `param` from the domain and replacing all top-level occurrences + * of the parameter elsewhere in the constraint by type `tp`, or a conservative + * approximation of it if that is needed to avoid cycles. + * Occurrences nested inside a refinement or prefix are not affected. */ - def transformed(poly: PolyType, op: Type => Type)(implicit ctx: Context) : Constraint = - updateEntries(poly, myMap(poly) map op) - - /** A new constraint with all entries coming from `pt` removed. */ - def remove(pt: PolyType)(implicit ctx: Context) = - new Constraint( - myMap remove pt, - changeDependencies(dependents, pt, myMap(pt), removeDep, null)) + def replace(param: PolyParam, tp: Type)(implicit ctx: Context): This + /** Narrow one of the bounds of type parameter `param` + * If `isUpper` is true, ensure that `param <: `bound`, otherwise ensure + * that `param >: bound`. + */ + def narrowBound(param: PolyParam, bound: Type, isUpper: Boolean)(implicit ctx: Context): This + /** Is entry associated with `pt` removable? * @param removedParam The index of a parameter which is still present in the * entry array, but is going to be removed at the same step, * or -1 if no such parameter exists. */ - def isRemovable(pt: PolyType, removedParam: Int = -1): Boolean = { - val entries = myMap(pt) - var noneLeft = true - var i = paramCount(entries) - while (noneLeft && i > 0) { - i -= 1 - if (i != removedParam && isBounds(entries(i))) noneLeft = false - else typeVar(entries, i) match { - case tv: TypeVar => - if (!tv.inst.exists) noneLeft = false // need to keep line around to compute instType - case _ => - } - } - noneLeft - } - - /** Drop parameter `PolyParam(poly, n)` from `bounds`, - * replacing with Nothing in the lower bound and by `Any` in the upper bound. - */ - def dropParamIn(bounds: TypeBounds, poly: PolyType, n: Int)(implicit ctx: Context): TypeBounds = { - def drop(tp: Type): Type = tp match { - case tp: AndOrType => - val tp1 = drop(tp.tp1) - val tp2 = drop(tp.tp2) - if (!tp1.exists) tp2 - else if (!tp2.exists) tp1 - else tp - case PolyParam(`poly`, `n`) => NoType - case _ => tp - } - def approx(tp: Type, limit: Type): Type = { - val tp1 = drop(tp) - if (tp1.exists || !tp.exists) tp1 else limit - } - bounds.derivedTypeBounds( - approx(bounds.lo, defn.NothingType), approx(bounds.hi, defn.AnyType)) - } - - /** A new constraint which is derived from this constraint by removing - * the type parameter `param` from the domain and replacing all occurrences - * of the parameter elsewhere in the constraint by type `tp`. - */ - private def uncheckedReplace(param: PolyParam, tp: Type)(implicit ctx: Context): Constraint = { - - def subst(poly: PolyType, entries: Array[Type]) = { - var result = entries - var i = 0 - while (i < paramCount(entries)) { - entries(i) match { - case oldBounds: TypeBounds => - val newBounds = oldBounds.substParam(param, tp).asInstanceOf[TypeBounds] - if (oldBounds ne newBounds) { - if (result eq entries) result = entries.clone - result(i) = dropParamIn(newBounds, poly, i) - } - case _ => - } - i += 1 - } - result - } - - val pt = param.binder - val constr1 = if (isRemovable(pt, param.paramNum)) remove(pt) else updated(param, tp) - val substMap = constr1.myMap mapValues subst - val result = new Constraint( - substMap, - diffDependencies(constr1.dependents, constr1.myMap, substMap)) - if (Config.checkConstraintsNonCyclic) result.checkNonCyclic() - result - } - - /** A new constraint which is derived from this constraint by removing - * the type parameter `param` from the domain and replacing all occurrences - * of the parameter elsewhere in the constraint by type `tp`. - * `tp` is another polyparam, applies the necessary unifications to avoud a cyclic - * constraint. - */ - def replace(param: PolyParam, tp: Type)(implicit ctx: Context): Constraint = - tp.dealias.stripTypeVar match { - case tp: PolyParam if this contains tp => - val bs = bounds(tp) - if (tp == param) - this - else if (param.occursIn(bs.lo, fromBelow = true) || - param.occursIn(bs.hi, fromBelow = false)) - unify(tp, param).uncheckedReplace(param, tp) - else - uncheckedReplace(param, tp) - case _ => - uncheckedReplace(param, tp) - } - - /** A constraint resulting by adding p2 = p1 to this constraint, and at the same - * time transferring all bounds of p2 to p1 - */ - def unify(p1: PolyParam, p2: PolyParam)(implicit ctx: Context): Constraint = { - val p1Bounds = - dropParamIn(bounds(p1), p2.binder, p2.paramNum) & - dropParamIn(bounds(p2), p1.binder, p1.paramNum) - this.updated(p1, p1Bounds).updated(p2, TypeAlias(p1)) - } - - /** A new constraint which is derived from this constraint by adding - * entries for all type parameters of `poly`. - */ - def add(poly: PolyType, tvars: List[TypeVar] = Nil)(implicit ctx: Context) : Constraint = { - val nparams = poly.paramNames.length - val entries = new Array[Type](nparams * 2) - poly.paramBounds.copyToArray(entries, 0) - tvars.copyToArray(entries, nparams) - updateEntries(poly, entries) - } + def isRemovable(pt: PolyType, removedParam: Int = -1): Boolean + + /** A new constraint with all entries coming from `pt` removed. */ + def remove(pt: PolyType)(implicit ctx: Context): This /** The polytypes constrained by this constraint */ - def domainPolys: List[PolyType] = myMap.keys + def domainPolys: List[PolyType] /** The polytype parameters constrained by this constraint */ - def domainParams: List[PolyParam] = - for { - (poly, entries) <- myMap.toList - n <- 0 until paramCount(entries) - if isBounds(entries(n)) - } yield PolyParam(poly, n) + def domainParams: List[PolyParam] - /** Check whether predicate holds for all parameters in constraint - */ - def forallParams(p: PolyParam => Boolean): Boolean = { - myMap.foreachBinding { (poly, entries) => - for (i <- 0 until paramCount(entries)) - if (isBounds(entries(i)) && !p(PolyParam(poly, i))) return false - } - true - } + /** Check whether predicate holds for all parameters in constraint */ + def forallParams(p: PolyParam => Boolean): Boolean /** Perform operation `op` on all typevars, or only on uninstantiated * typevars, depending on whether `uninstOnly` is set or not. */ - def foreachTypeVar(op: TypeVar => Unit): Unit = - myMap.foreachBinding { (poly, entries) => - for (i <- 0 until paramCount(entries)) { - typeVar(entries, i) match { - case tv: TypeVar if !tv.inst.exists => op(tv) - case _ => - } - } - } - - private var myUninstVars: mutable.ArrayBuffer[TypeVar] = null + def foreachTypeVar(op: TypeVar => Unit): Unit /** The uninstantiated typevars of this constraint */ - def uninstVars: collection.Seq[TypeVar] = { - if (myUninstVars == null) { - myUninstVars = new mutable.ArrayBuffer[TypeVar] - myMap.foreachBinding { (poly, entries) => - for (i <- 0 until paramCount(entries)) { - typeVar(entries, i) match { - case tv: TypeVar if isBounds(entries(i)) => myUninstVars += tv - case _ => - } - } - } - } - myUninstVars - } - - def constrainedTypesText(printer: Printer): Text = - Text(domainPolys map (_.toText(printer)), ", ") + def uninstVars: collection.Seq[TypeVar] - def constraintText(indent: Int, printer: Printer): Text = { - val assocs = - for (param <- domainParams) - yield (" " * indent) ~ param.toText(printer) ~ at(param).toText(printer) - Text(assocs, "\n") - } - - override def toText(printer: Printer): Text = { - val header: Text = "Constraint(" - val uninstVarsText = " uninstVars = " ~ - Text(uninstVars map (_.toText(printer)), ", ") ~ ";" - val constrainedText = - " constrained types = " ~ constrainedTypesText(printer) ~ ";" - val constraintsText = - " constraint = " ~ constraintText(3, printer) ~ ")" - Text.lines(List(header, uninstVarsText, constrainedText, constraintsText)) - } -} - -trait ConstraintRunInfo { self: RunInfo => - private var maxSize = 0 - private var maxConstraint: Constraint = _ - def recordConstraintSize(c: Constraint, size: Int) = - if (size > maxSize) { - maxSize = size - maxConstraint = c - } - def printMaxConstraint()(implicit ctx: Context) = - if (maxSize > 0) typr.println(s"max constraint = ${maxConstraint.show}") + /** Check that no constrained parameter contains itself as a bound */ + def checkNonCyclic()(implicit ctx: Context): Unit } diff --git a/src/dotty/tools/dotc/core/ConstraintHandling.scala b/src/dotty/tools/dotc/core/ConstraintHandling.scala new file mode 100644 index 000000000000..02f5bf87f4cb --- /dev/null +++ b/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -0,0 +1,256 @@ +package dotty.tools +package dotc +package core + +import Types._, Contexts._, Symbols._ +import Decorators._ +import config.Config +import config.Printers._ + +/** Methods for adding constraints and solving them. + * + * Constraints are required to be in normalized form. This means + * (1) if P <: Q in C then also Q >: P in C + * (2) if P r Q in C and Q r R in C then also P r R in C, where r is <: or :> + * + * "P <: Q in C" means here: There is a constraint P <: H[Q], + * where H is the multi-hole context given by: + * + * H = [] + * H & T + * T & H + * H | H + * + * (the idea is that a parameter Q in a H context is guaranteed to be a supertype of P). + * + * "P >: Q in C" means: There is a constraint P >: L[Q], + * where L is the multi-hole context given by: + * + * L = [] + * L | T + * T | L + * L & L + */ +trait ConstraintHandling { + + implicit val ctx: Context + + protected def isSubType(tp1: Type, tp2: Type): Boolean + + val state: TyperState + import state.constraint + + private var addConstraintInvocations = 0 + + /** If the constraint is frozen we cannot add new bounds to the constraint. */ + protected var frozenConstraint = false + + private def addOneBound(param: PolyParam, bound: Type, isUpper: Boolean): Boolean = + !constraint.contains(param) || { + val c1 = constraint.narrowBound(param, bound, isUpper) + (c1 eq constraint) || { + constraint = c1 + val TypeBounds(lo, hi) = constraint.entry(param) + isSubType(lo, hi) + } + } + + protected def addUpperBound(param: PolyParam, 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.flagInstantiationToNothing) assert(false, msg) + else ctx.log(msg) + } + constr.println(i"adding $description") + val lower = constraint.lower(param) + val res = + addOneBound(param, bound, isUpper = true) && + lower.forall(addOneBound(_, bound, isUpper = true)) + constr.println(i"added $description = $res") + res + } + + protected def addLowerBound(param: PolyParam, bound: Type): Boolean = { + def description = i"constraint $param >: $bound to\n$constraint" + constr.println(i"adding $description") + val upper = constraint.upper(param) + val res = + addOneBound(param, bound, isUpper = false) && + upper.forall(addOneBound(_, bound, isUpper = false)) + constr.println(i"added $description = $res") + res + } + + protected def addLess(p1: PolyParam, p2: PolyParam): Boolean = { + def description = i"ordering $p1 <: $p2 to\n$constraint" + val res = + if (constraint.isLess(p2, p1)) unify(p2, p1) + // !!! this is direction dependent - unify(p1, p2) makes i94-nada loop forever. + // Need to investigate why this is the case. + // The symptom is that we get a subtyping constraint of the form P { ... } <: P + else { + val down1 = p1 :: constraint.exclusiveLower(p1, p2) + 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") + constraint = constraint.addLess(p1, p2) + down1.forall(addOneBound(_, hi2, isUpper = true)) && + up2.forall(addOneBound(_, lo1, isUpper = false)) + } + constr.println(i"added $description = $res") + res + } + + /** Make p2 = p1, transfer all bounds of p2 to p1 + * @pre less(p1)(p2) + */ + private def unify(p1: PolyParam, p2: PolyParam): Boolean = { + constr.println(s"unifying $p1 $p2") + assert(constraint.isLess(p1, p2)) + val down = constraint.exclusiveLower(p2, p1) + val up = constraint.exclusiveUpper(p1, p2) + constraint = constraint.unify(p1, p2) + val bounds = constraint.nonParamBounds(p1) + val lo = bounds.lo + val hi = bounds.hi + isSubType(lo, hi) && + down.forall(addOneBound(_, hi, isUpper = true)) && + up.forall(addOneBound(_, lo, isUpper = false)) + } + + protected final def isSubTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = { + val saved = frozenConstraint + frozenConstraint = true + try isSubType(tp1, tp2) + finally frozenConstraint = saved + } + + /** Test whether the lower bounds of all parameters in this + * constraint are a solution to the constraint. + */ + protected final def isSatisfiable: Boolean = + constraint.forallParams { param => + val TypeBounds(lo, hi) = constraint.entry(param) + isSubType(lo, hi) || { + ctx.log(i"sub fail $lo <:< $hi") + false + } + } + + /** Solve constraint set for given type parameter `param`. + * If `fromBelow` is true the parameter is approximated by its lower bound, + * otherwise it is approximated by its upper bound. However, any occurrences + * of the parameter in a refinement somewhere in the bound are removed. + * (Such occurrences can arise for F-bounded types). + * The constraint is left unchanged. + * @return the instantiating type + * @pre `param` is in the constraint's domain. + */ + final def approximation(param: PolyParam, fromBelow: Boolean): Type = { + val avoidParam = new TypeMap { + override def stopAtStatic = true + def apply(tp: Type) = mapOver { + tp match { + case tp: RefinedType if param occursIn tp.refinedInfo => tp.parent + case _ => tp + } + } + } + val bound = if (fromBelow) constraint.fullLowerBound(param) else constraint.fullUpperBound(param) + val inst = avoidParam(bound) + typr.println(s"approx ${param.show}, from below = $fromBelow, bound = ${bound.show}, inst = ${inst.show}") + inst + } + + /** Constraint `c1` subsumes constraint `c2`, if under `c2` as constraint we have + * for all poly params `p` defined in `c2` as `p >: L2 <: U2`: + * + * c1 defines p with bounds p >: L1 <: U1, and + * L2 <: L1, and + * U1 <: U2 + * + * Both `c1` and `c2` are required to derive from constraint `pre`, possibly + * narrowing it with further bounds. + */ + protected final def subsumes(c1: Constraint, c2: Constraint, pre: Constraint): Boolean = + if (c2 eq pre) true + else if (c1 eq pre) false + else { + val saved = constraint + try + c2.forallParams(p => + c1.contains(p) && + c2.upper(p).forall(c1.isLess(p, _)) && + isSubTypeWhenFrozen(c1.nonParamBounds(p), c2.nonParamBounds(p))) + finally constraint = saved + } + + /** The current bounds of type parameter `param` */ + final def bounds(param: PolyParam): TypeBounds = constraint.entry(param) match { + case bounds: TypeBounds => bounds + case _ => param.binder.paramBounds(param.paramNum) + } + + /** If `p` is a parameter of `pt`, propagate the non-parameter bounds + * of `p` to the parameters known to be less or greater than `p`. + */ + def initialize(pt: PolyType): Boolean = + checkPropagated(i"initialized $pt") { + pt.paramNames.indices.forall { i => + val param = PolyParam(pt, i) + val bounds = constraint.nonParamBounds(param) + val lower = constraint.lower(param) + val upper = constraint.upper(param) + if (lower.nonEmpty && !bounds.lo.isRef(defn.NothingClass) || + upper.nonEmpty && !bounds.hi.isRef(defn.AnyClass)) println(i"INIT*** $pt") + lower.forall(addOneBound(_, bounds.hi, isUpper = true)) && + upper.forall(addOneBound(_, bounds.lo, isUpper = false)) + } + } + + /** Can `param` be constrained with new bounds? */ + final def canConstrain(param: PolyParam): Boolean = + !frozenConstraint && (constraint contains param) + + /** Add constraint `param <: bond` if `fromBelow` is true, `param >: bound` otherwise. + * `bound` is assumed to be in normalized form, as specified in `firstTry` and + * `secondTry` of `TypeComparer`. In particular, it should not be an alias type, + * lazy ref, typevar, wildcard type, error type. In addition, upper bounds may + * not be AndTypes and lower bounds may not be OrTypes. + */ + protected def addConstraint(param: PolyParam, bound: Type, fromBelow: Boolean): Boolean = { + def description = i"constr $param ${if (fromBelow) ">:" else "<:"} $bound:\n$constraint" + //checkPropagated(s"adding $description")(true) // DEBUG in case following fails + checkPropagated(s"added $description") { + addConstraintInvocations += 1 + try bound match { + case bound: PolyParam if constraint contains bound => + if (fromBelow) addLess(bound, param) else addLess(param, bound) + case _ => + if (fromBelow) addLowerBound(param, bound) else addUpperBound(param, bound) + } + finally addConstraintInvocations -= 1 + } + } + + /** Check that constraint is fully propagated. See comment in Config.checkConstraintsPropagated */ + def checkPropagated(msg: => String)(result: Boolean): Boolean = { + if (Config.checkConstraintsPropagated && result && addConstraintInvocations == 0) { + frozenConstraint = true + for (p <- constraint.domainParams) { + def check(cond: => Boolean, q: PolyParam, ordering: String, explanation: String): Unit = + assert(cond, i"propagation failure for $p $ordering $q: $explanation\n$msg") + for (u <- constraint.upper(p)) + check(bounds(p).hi <:< bounds(u).hi, u, "<:", "upper bound not propagated") + for (l <- constraint.lower(p)) { + check(bounds(l).lo <:< bounds(p).hi, l, ">:", "lower bound not propagated") + check(constraint.isLess(l, p), l, ">:", "reverse ordering (<:) missing") + } + } + frozenConstraint = false + } + result + } +} diff --git a/src/dotty/tools/dotc/core/ConstraintRunInfo.scala b/src/dotty/tools/dotc/core/ConstraintRunInfo.scala new file mode 100644 index 000000000000..4b7e22653e0b --- /dev/null +++ b/src/dotty/tools/dotc/core/ConstraintRunInfo.scala @@ -0,0 +1,16 @@ +package dotty.tools.dotc +package core + +import Contexts._, config.Printers._ + +trait ConstraintRunInfo { self: RunInfo => + private var maxSize = 0 + private var maxConstraint: Constraint = _ + def recordConstraintSize(c: Constraint, size: Int) = + if (size > maxSize) { + maxSize = size + maxConstraint = c + } + def printMaxConstraint()(implicit ctx: Context) = + if (maxSize > 0) typr.println(s"max constraint = ${maxConstraint.show}") +} diff --git a/src/dotty/tools/dotc/core/NameOps.scala b/src/dotty/tools/dotc/core/NameOps.scala index 240447e6d43c..c15c4c4c2946 100644 --- a/src/dotty/tools/dotc/core/NameOps.scala +++ b/src/dotty/tools/dotc/core/NameOps.scala @@ -355,5 +355,5 @@ object NameOps { case NO_NAME => primitivePostfixMethodName case name => name } - } + } } \ No newline at end of file diff --git a/src/dotty/tools/dotc/core/OrderingConstraint.scala b/src/dotty/tools/dotc/core/OrderingConstraint.scala new file mode 100644 index 000000000000..ec2a654165c7 --- /dev/null +++ b/src/dotty/tools/dotc/core/OrderingConstraint.scala @@ -0,0 +1,502 @@ +package dotty.tools +package dotc +package core + +import Types._, Contexts._, Symbols._, Decorators._ +import util.SimpleMap +import collection.mutable +import printing.{Printer, Showable} +import printing.Texts._ +import config.Config +import config.Printers._ +import collection.immutable.BitSet +import reflect.ClassTag + +object OrderingConstraint { + + /** The type of `Constraint#boundsMap` */ + type ParamBounds = SimpleMap[PolyType, Array[Type]] + + /** The type of `Constraint#lowerMap`, `Constraint#upperMap` */ + type ParamOrdering = SimpleMap[PolyType, Array[List[PolyParam]]] + + /** A new constraint with given maps */ + private def newConstraint(boundsMap: ParamBounds, lowerMap: ParamOrdering, upperMap: ParamOrdering)(implicit ctx: Context) : OrderingConstraint = { + val result = new OrderingConstraint(boundsMap, lowerMap, upperMap) + if (Config.checkConstraintsNonCyclic) result.checkNonCyclic() + ctx.runInfo.recordConstraintSize(result, result.boundsMap.size) + result + } + + /** A lens for updating a single entry array in one of the three constraint maps */ + abstract class ConstraintLens[T <: AnyRef: ClassTag] { + def entries(c: OrderingConstraint, poly: PolyType): Array[T] + def updateEntries(c: OrderingConstraint, poly: PolyType, entries: Array[T])(implicit ctx: Context): OrderingConstraint + def initial: T + + def apply(c: OrderingConstraint, poly: PolyType, idx: Int) = { + val es = entries(c, poly) + if (es == null) initial else es(idx) + } + + /** The `current` constraint but with the entry for `param` updated to `entry`. + * `current` is used linearly. If it is different from `prev` it is + * known to be dead after the call. Hence it is OK to update destructively + * parts of `current` which are not shared by `prev`. + */ + def update(prev: OrderingConstraint, current: OrderingConstraint, + poly: PolyType, idx: Int, entry: T)(implicit ctx: Context): OrderingConstraint = { + var es = entries(current, poly) + if (es != null && (es(idx) eq entry)) current + else { + val result = + if (es == null) { + es = Array.fill(poly.paramNames.length)(initial) + updateEntries(current, poly, es) + } + else if (es ne entries(prev, poly)) + current // can re-use existing entries array. + else { + es = es.clone + updateEntries(current, poly, es) + } + es(idx) = entry + result + } + } + + def update(prev: OrderingConstraint, current: OrderingConstraint, + param: PolyParam, entry: T)(implicit ctx: Context): OrderingConstraint = + update(prev, current, param.binder, param.paramNum, entry) + + def map(prev: OrderingConstraint, current: OrderingConstraint, + poly: PolyType, idx: Int, f: T => T)(implicit ctx: Context): OrderingConstraint = + update(prev, current, poly, idx, f(apply(current, poly, idx))) + + def map(prev: OrderingConstraint, current: OrderingConstraint, + param: PolyParam, f: T => T)(implicit ctx: Context): OrderingConstraint = + map(prev, current, param.binder, param.paramNum, f) + } + + val boundsLens = new ConstraintLens[Type] { + def entries(c: OrderingConstraint, poly: PolyType): Array[Type] = + c.boundsMap(poly) + def updateEntries(c: OrderingConstraint, poly: PolyType, entries: Array[Type])(implicit ctx: Context): OrderingConstraint = + newConstraint(c.boundsMap.updated(poly, entries), c.lowerMap, c.upperMap) + def initial = NoType + } + + val lowerLens = new ConstraintLens[List[PolyParam]] { + def entries(c: OrderingConstraint, poly: PolyType): Array[List[PolyParam]] = + c.lowerMap(poly) + def updateEntries(c: OrderingConstraint, poly: PolyType, entries: Array[List[PolyParam]])(implicit ctx: Context): OrderingConstraint = + newConstraint(c.boundsMap, c.lowerMap.updated(poly, entries), c.upperMap) + def initial = Nil + } + + val upperLens = new ConstraintLens[List[PolyParam]] { + def entries(c: OrderingConstraint, poly: PolyType): Array[List[PolyParam]] = + c.upperMap(poly) + def updateEntries(c: OrderingConstraint, poly: PolyType, entries: Array[List[PolyParam]])(implicit ctx: Context): OrderingConstraint = + newConstraint(c.boundsMap, c.lowerMap, c.upperMap.updated(poly, entries)) + def initial = Nil + } +} + +import OrderingConstraint._ + +/** Constraint over undetermined type parameters that keeps separate maps to + * reflect parameter orderings. + * @param boundsMap a map from PolyType to arrays. + * Each array contains twice the number of entries as there a type parameters + * in the PolyType. The first half of the array contains the type bounds that constrain the + * polytype's type parameters. The second half might contain type variables that + * track the corresponding parameters, or is left empty (filled with nulls). + * An instantiated type parameter is represented by having its instance type in + * the corresponding array entry. + * + * @param lowerMap a map from PolyTypes to arrays. Each array entry corresponds + * to a parameter P of the polytype; it contains all constrained parameters + * Q that are known to be smaller than P, i.e. P <: Q. + * @param upperMap a map from PolyTypes to arrays. Each array entry corresponds + * to a parameter P of the polytype; it contains all constrained parameters + * Q that are known to be greater than P, i.e. Q <: P. + */ +class OrderingConstraint(private val boundsMap: ParamBounds, + private val lowerMap : ParamOrdering, + private val upperMap : ParamOrdering) extends Constraint { + + type This = OrderingConstraint + + +// ----------- Basic indices -------------------------------------------------- + + /** The number of type parameters in the given entry array */ + private def paramCount(entries: Array[Type]) = entries.length >> 1 + + /** The type variable corresponding to parameter numbered `n`, null if none was created */ + private def typeVar(entries: Array[Type], n: Int): Type = + entries(paramCount(entries) + n) + + /** The `boundsMap` entry corresponding to `param` */ + def entry(param: PolyParam): Type = { + val entries = boundsMap(param.binder) + if (entries == null) NoType + else entries(param.paramNum) + } + +// ----------- Contains tests -------------------------------------------------- + + def contains(pt: PolyType): Boolean = boundsMap(pt) != null + + def contains(param: PolyParam): Boolean = { + val entries = boundsMap(param.binder) + entries != null && entries(param.paramNum).isInstanceOf[TypeBounds] + } + + def contains(tvar: TypeVar): Boolean = { + val origin = tvar.origin + val entries = boundsMap(origin.binder) + val pnum = origin.paramNum + entries != null && isBounds(entries(pnum)) && (typeVar(entries, pnum) eq tvar) + } + + private def isBounds(tp: Type) = tp.isInstanceOf[TypeBounds] + +// ---------- Dependency handling ---------------------------------------------- + + def lower(param: PolyParam): List[PolyParam] = lowerLens(this, param.binder, param.paramNum) + def upper(param: PolyParam): List[PolyParam] = upperLens(this, param.binder, param.paramNum) + + def minLower(param: PolyParam): List[PolyParam] = { + val all = lower(param) + all.filterNot(p => all.exists(isLess(p, _))) + } + + def minUpper(param: PolyParam): List[PolyParam] = { + val all = upper(param) + all.filterNot(p => all.exists(isLess(_, p))) + } + + def exclusiveLower(param: PolyParam, butNot: PolyParam): List[PolyParam] = + lower(param).filterNot(isLess(_, butNot)) + + def exclusiveUpper(param: PolyParam, butNot: PolyParam): List[PolyParam] = + upper(param).filterNot(isLess(butNot, _)) + +// ---------- Info related to PolyParams ------------------------------------------- + + def isLess(param1: PolyParam, param2: PolyParam): Boolean = + upper(param1).contains(param2) + + def nonParamBounds(param: PolyParam): TypeBounds = + entry(param).asInstanceOf[TypeBounds] + + def fullLowerBound(param: PolyParam)(implicit ctx: Context): Type = + (nonParamBounds(param).lo /: minLower(param))(_ | _) + + def fullUpperBound(param: PolyParam)(implicit ctx: Context): Type = + (nonParamBounds(param).hi /: minUpper(param))(_ & _) + + def fullBounds(param: PolyParam)(implicit ctx: Context): TypeBounds = + nonParamBounds(param).derivedTypeBounds(fullLowerBound(param), fullUpperBound(param)) + + def typeVarOfParam(param: PolyParam): Type = { + val entries = boundsMap(param.binder) + if (entries == null) NoType + else { + val tvar = typeVar(entries, param.paramNum) + if (tvar != null) tvar else NoType + } + } + +// ---------- Adding PolyTypes -------------------------------------------------- + + /** The bound type `tp` without dependent parameters + * NoType if type consists only of dependent parameters. + * @param seenFromBelow If true, `bound` is an upper bound, else a lower bound. + */ + private def stripParams(tp: Type, paramBuf: mutable.ListBuffer[PolyParam], + isUpper: Boolean)(implicit ctx: Context): Type = tp match { + case param: PolyParam if contains(param) => + if (!paramBuf.contains(param)) paramBuf += param + NoType + case tp: AndOrType if isUpper == tp.isAnd => + val tp1 = stripParams(tp.tp1, paramBuf, isUpper) + val tp2 = stripParams(tp.tp2, paramBuf, isUpper) + if (tp1.exists) + if (tp2.exists) tp.derivedAndOrType(tp1, tp2) + else tp1 + else tp2 + case _ => + tp + } + + /** The bound type `tp` without dependent parameters. + * A top or bottom type if type consists only of dependent parameters. + * @param seenFromBelow If true, `bound` is an upper bound, else a lower bound. + */ + private def normalizedType(tp: Type, paramBuf: mutable.ListBuffer[PolyParam], + isUpper: Boolean)(implicit ctx: Context): Type = + stripParams(tp, paramBuf, isUpper) + .orElse(if (isUpper) defn.AnyType else defn.NothingType) + + def add(poly: PolyType, tvars: List[TypeVar])(implicit ctx: Context): This = { + assert(!contains(poly)) + val nparams = poly.paramNames.length + val entries1 = new Array[Type](nparams * 2) + poly.paramBounds.copyToArray(entries1, 0) + tvars.copyToArray(entries1, nparams) + newConstraint(boundsMap.updated(poly, entries1), lowerMap, upperMap).init(poly) + } + + private def init(poly: PolyType)(implicit ctx: Context): This = { + var current = this + val loBuf, hiBuf = new mutable.ListBuffer[PolyParam] + var i = 0 + while (i < poly.paramNames.length) { + val param = PolyParam(poly, i) + val bounds = nonParamBounds(param) + val lo = normalizedType(bounds.lo, loBuf, isUpper = false) + val hi = normalizedType(bounds.hi, hiBuf, isUpper = true) + current = boundsLens.update(this, current, poly, i, bounds.derivedTypeBounds(lo, hi)) + current = (current /: loBuf)(order(_, _, param)) + current = (current /: hiBuf)(order(_, param, _)) + loBuf.clear() + hiBuf.clear() + i += 1 + } + if (Config.checkConstraintsNonCyclic) checkNonCyclic() + current + } + +// ---------- Updates ------------------------------------------------------------ + + /** An updated partial order matrix that incorporates `less` and also reflects that `param` relates + * to `p2` wrt <:< if `inOrder` is true, `>:>` otherwise. + */ + private def order(current: This, param1: PolyParam, param2: PolyParam)(implicit ctx: Context): This = + if (param1 == param2 || current.isLess(param1, param2)) this + else { + assert(contains(param1)) + assert(contains(param2)) + val newUpper = param2 :: exclusiveUpper(param2, param1) + val newLower = param1 :: exclusiveLower(param1, param2) + val current1 = (current /: newLower)(upperLens.map(this, _, _, newUpper ::: _)) + val current2 = (current1 /: newUpper)(lowerLens.map(this, _, _, newLower ::: _)) + current2 + } + + def addLess(param1: PolyParam, param2: PolyParam)(implicit ctx: Context): This = + order(this, param1, param2) + + def updateEntry(param: PolyParam, tp: Type)(implicit ctx: Context): This = + boundsLens.update(this, this, param, tp) + + def unify(p1: PolyParam, p2: PolyParam)(implicit ctx: Context): This = { + val p1Bounds = (nonParamBounds(p1) & nonParamBounds(p2)).substParam(p2, p1) + updateEntry(p1, p1Bounds).replace(p2, p1) + } + + def narrowBound(param: PolyParam, bound: Type, isUpper: Boolean)(implicit ctx: Context): This = { + val oldBounds @ TypeBounds(lo, hi) = nonParamBounds(param) + val newBounds = + if (isUpper) oldBounds.derivedTypeBounds(lo, hi & bound) + else oldBounds.derivedTypeBounds(lo | bound, hi) + updateEntry(param, newBounds) + } + +// ---------- Removals ------------------------------------------------------------ + + /** A new constraint which is derived from this constraint by removing + * the type parameter `param` from the domain and replacing all top-level occurrences + * of the parameter elsewhere in the constraint by type `tp`, or a conservative + * approximation of it if that is needed to avoid cycles. + * Occurrences nested inside a refinement or prefix are not affected. + * + * The reason we need to substitute top-level occurrences of the parameter + * is to deal with situations like the following. Say we have in the constraint + * + * P <: Q & String + * Q + * + * and we replace Q with P. Then substitution gives + * + * P <: P & String + * + * this would be a cyclic constraint is therefore changed by `normalize` and + * `recombine` below to + * + * P <: String + * + * approximating the RHS occurrence of P with Any. Without the substitution we + * would not find out where we need to approximate. Occurrences of parameters + * that are not top-level are not affected. + */ + def replace(param: PolyParam, tp: Type)(implicit ctx: Context): OrderingConstraint = { + val replacement = tp.dealias.stripTypeVar + if (param == replacement) this + else { + assert(replacement.isValueType) + val poly = param.binder + val idx = param.paramNum + + def removeParam(ps: List[PolyParam]) = + ps.filterNot(p => p.binder.eq(poly) && p.paramNum == idx) + + def replaceParam(tp: Type, atPoly: PolyType, atIdx: Int) = tp match { + case bounds @ TypeBounds(lo, hi) => + + def recombine(andor: AndOrType, op: (Type, Boolean) => Type, isUpper: Boolean): Type = { + val tp1 = op(andor.tp1, isUpper) + val tp2 = op(andor.tp2, isUpper) + if ((tp1 eq andor.tp1) && (tp2 eq andor.tp2)) andor + else if (andor.isAnd) tp1 & tp2 + else tp1 | tp2 + } + + def normalize(tp: Type, isUpper: Boolean): Type = tp match { + case p: PolyParam if p.binder == atPoly && p.paramNum == atIdx => + if (isUpper) defn.AnyType else defn.NothingType + case tp: AndOrType if isUpper == tp.isAnd => recombine(tp, normalize, isUpper) + case _ => tp + } + + def replaceIn(tp: Type, isUpper: Boolean): Type = tp match { + case `param` => normalize(replacement, isUpper) + case tp: AndOrType if isUpper == tp.isAnd => recombine(tp, replaceIn, isUpper) + case _ => tp + } + + bounds.derivedTypeBounds(replaceIn(lo, isUpper = false), replaceIn(hi, isUpper = true)) + case _ => tp + } + + var current = + if (isRemovable(poly, idx)) remove(poly) else updateEntry(param, replacement) + current.foreachParam {(p, i) => + current = boundsLens.map(this, current, p, i, replaceParam(_, p, i)) + current = lowerLens.map(this, current, p, i, removeParam) + current = upperLens.map(this, current, p, i, removeParam) + } + current + } + } + + def remove(pt: PolyType)(implicit ctx: Context): This = + newConstraint(boundsMap.remove(pt), lowerMap.remove(pt), upperMap.remove(pt)) + + def isRemovable(pt: PolyType, removedParam: Int = -1): Boolean = { + val entries = boundsMap(pt) + var noneLeft = true + var i = paramCount(entries) + while (noneLeft && i > 0) { + i -= 1 + if (i != removedParam && isBounds(entries(i))) noneLeft = false + else typeVar(entries, i) match { + case tv: TypeVar => + if (!tv.inst.exists) noneLeft = false // need to keep line around to compute instType + case _ => + } + } + noneLeft + } + +// ---------- Exploration -------------------------------------------------------- + + def domainPolys: List[PolyType] = boundsMap.keys + + def domainParams: List[PolyParam] = + for { + (poly, entries) <- boundsMap.toList + n <- 0 until paramCount(entries) + if isBounds(entries(n)) + } yield PolyParam(poly, n) + + def forallParams(p: PolyParam => Boolean): Boolean = { + boundsMap.foreachBinding { (poly, entries) => + for (i <- 0 until paramCount(entries)) + if (isBounds(entries(i)) && !p(PolyParam(poly, i))) return false + } + true + } + + def foreachParam(p: (PolyType, Int) => Unit): Unit = + boundsMap.foreachBinding { (poly, entries) => + 0.until(poly.paramNames.length).foreach(p(poly, _)) + } + + def foreachTypeVar(op: TypeVar => Unit): Unit = + boundsMap.foreachBinding { (poly, entries) => + for (i <- 0 until paramCount(entries)) { + typeVar(entries, i) match { + case tv: TypeVar if !tv.inst.exists => op(tv) + case _ => + } + } + } + + private var myUninstVars: mutable.ArrayBuffer[TypeVar] = _ + + /** The uninstantiated typevars of this constraint */ + def uninstVars: collection.Seq[TypeVar] = { + if (myUninstVars == null) { + myUninstVars = new mutable.ArrayBuffer[TypeVar] + boundsMap.foreachBinding { (poly, entries) => + for (i <- 0 until paramCount(entries)) { + typeVar(entries, i) match { + case tv: TypeVar if !tv.inst.exists && isBounds(entries(i)) => myUninstVars += tv + case _ => + } + } + } + } + myUninstVars + } + +// ---------- Cyclic checking ------------------------------------------- + + def checkNonCyclic()(implicit ctx: Context): Unit = + domainParams.foreach(checkNonCyclic) + + private def checkNonCyclic(param: PolyParam)(implicit ctx: Context): Unit = + assert(!isLess(param, param), i"cyclic constraint involving $param in $this") + +// ---------- toText ----------------------------------------------------- + + override def toText(printer: Printer): Text = { + def entryText(tp: Type) = tp match { + case tp: TypeBounds => + tp.toText(printer) + case _ => + " := " ~ tp.toText(printer) + } + val indent = 3 + val header: Text = "Constraint(" + val uninstVarsText = " uninstVars = " ~ + Text(uninstVars map (_.toText(printer)), ", ") ~ ";" + val constrainedText = + " constrained types = " ~ Text(domainPolys map (_.toText(printer)), ", ") + val boundsText = + " bounds = " ~ { + val assocs = + for (param <- domainParams) + yield (" " * indent) ~ param.toText(printer) ~ entryText(entry(param)) + Text(assocs, "\n") + } + val orderingText = + " ordering = " ~ { + val deps = + for { + param <- domainParams + ups = minUpper(param) + if ups.nonEmpty + } + yield + (" " * indent) ~ param.toText(printer) ~ " <: " ~ + Text(ups.map(_.toText(printer)), ", ") + Text(deps, "\n") + } + Text.lines(List(header, uninstVarsText, constrainedText, boundsText, orderingText, ")")) + } +} \ No newline at end of file diff --git a/src/dotty/tools/dotc/core/Skolemization.scala b/src/dotty/tools/dotc/core/Skolemization.scala new file mode 100644 index 000000000000..8bc5c815f72c --- /dev/null +++ b/src/dotty/tools/dotc/core/Skolemization.scala @@ -0,0 +1,126 @@ +package dotty.tools.dotc +package core + +import Symbols._, Types._, Contexts._ +import collection.mutable + +/** Methods to add and remove skolemtypes. + * + * Skolem types are generated when comparing refinements. + * A skolem type is simply a fresh singleton type that has a given type + * as underlying type. + * Two skolem types are equal if they refer to the same underlying type. + * To avoid unsoundness, skolem types have to be kept strictly local to the + * comparison, they are not allowed to escape the lifetime of a comparison + * by surviving in a context or in GADT bounds. + */ +trait Skolemization { + + implicit val ctx: Context + + protected var skolemsOutstanding = false + + def ensureSingleton(tp: Type): SingletonType = tp.stripTypeVar match { + case tp: SingletonType => + tp + case tp: ValueType => + skolemsOutstanding = true + SkolemType(tp) + case tp: TypeProxy => + ensureSingleton(tp.underlying) + } + + /** Approximate a type `tp` with a type that does not contain skolem types. + * @param toSuper if true, return the smallest supertype of `tp` with this property + * else return the largest subtype. + */ + final def deSkolemize(tp: Type, toSuper: Boolean): Type = + if (skolemsOutstanding) deSkolemize(tp, if (toSuper) 1 else -1, Set()) + else tp + + private def deSkolemize(tp: Type, variance: Int, seen: Set[SkolemType]): Type = + ctx.traceIndented(s"deskolemize $tp, variance = $variance, seen = $seen = ") { + def approx(lo: Type = defn.NothingType, hi: Type = defn.AnyType, newSeen: Set[SkolemType] = seen) = + if (variance == 0) NoType + else deSkolemize(if (variance < 0) lo else hi, variance, newSeen) + tp match { + case tp: SkolemType => + if (seen contains tp) NoType + else approx(hi = tp.binder, newSeen = seen + tp) + case tp: NamedType => + val sym = tp.symbol + if (sym.isStatic) tp + else { + val pre1 = deSkolemize(tp.prefix, variance, seen) + if (pre1.exists && !pre1.isRef(defn.NothingClass)) tp.derivedSelect(pre1) + else { + ctx.log(s"deskolem: $tp: ${tp.info}") + tp.info match { + case TypeBounds(lo, hi) => approx(lo, hi) + case info => approx(defn.NothingType, info) + } + } + } + case _: ThisType | _: BoundType | _: SuperType | NoType | NoPrefix => + tp + case tp: RefinedType => + val parent1 = deSkolemize(tp.parent, variance, seen) + if (parent1.exists) { + val refinedInfo1 = deSkolemize(tp.refinedInfo, variance, seen) + if (refinedInfo1.exists) + tp.derivedRefinedType(parent1, tp.refinedName, refinedInfo1) + else + approx(hi = parent1) + } + else approx() + case tp: TypeAlias => + val alias1 = deSkolemize(tp.alias, variance * tp.variance, seen) + if (alias1.exists) tp.derivedTypeAlias(alias1) + else approx(hi = TypeBounds.empty) + case tp: TypeBounds => + val lo1 = deSkolemize(tp.lo, -variance, seen) + val hi1 = deSkolemize(tp.hi, variance, seen) + if (lo1.exists && hi1.exists) tp.derivedTypeBounds(lo1, hi1) + else approx(hi = + if (lo1.exists) TypeBounds.lower(lo1) + else if (hi1.exists) TypeBounds.upper(hi1) + else TypeBounds.empty) + case tp: ClassInfo => + val pre1 = deSkolemize(tp.prefix, variance, seen) + if (pre1.exists) tp.derivedClassInfo(pre1) + else NoType + case tp: AndOrType => + val tp1d = deSkolemize(tp.tp1, variance, seen) + val tp2d = deSkolemize(tp.tp2, variance, seen) + if (tp1d.exists && tp2d.exists) + tp.derivedAndOrType(tp1d, tp2d) + else if (tp.isAnd) + approx(hi = tp1d & tp2d) // if one of tp1d, tp2d exists, it is the result of tp1d & tp2d + else + approx(lo = tp1d & tp2d) + case tp: WildcardType => + val bounds1 = deSkolemize(tp.optBounds, variance, seen) + if (bounds1.exists) tp.derivedWildcardType(bounds1) + else WildcardType + case _ => + if (tp.isInstanceOf[MethodicType]) assert(variance != 0, tp) + deSkolemizeMap.mapOver(tp, variance, seen) + } + } + + object deSkolemizeMap extends TypeMap { + private var seen: Set[SkolemType] = _ + def apply(tp: Type) = deSkolemize(tp, variance, seen) + def mapOver(tp: Type, variance: Int, seen: Set[SkolemType]) = { + val savedVariance = this.variance + val savedSeen = this.seen + this.variance = variance + this.seen = seen + try super.mapOver(tp) + finally { + this.variance = savedVariance + this.seen = savedSeen + } + } + } +} \ No newline at end of file diff --git a/src/dotty/tools/dotc/core/Substituters.scala b/src/dotty/tools/dotc/core/Substituters.scala index 02810733ad45..b45522bf94c1 100644 --- a/src/dotty/tools/dotc/core/Substituters.scala +++ b/src/dotty/tools/dotc/core/Substituters.scala @@ -1,6 +1,6 @@ package dotty.tools.dotc.core -import Types._, Symbols._, Contexts._ +import Types._, Symbols._, Contexts._, Names._ /** Substitution operations on types. See the corresponding `subst` and * `substThis` methods on class Type for an explanation. @@ -179,21 +179,21 @@ trait Substituters { this: Context => .mapOver(tp) } - final def substThis(tp: Type, from: RefinedType, to: Type, theMap: SubstRefinedThisMap): Type = + final def substSkolem(tp: Type, from: Type, to: Type, theMap: SubstSkolemMap): Type = tp match { - case tp @ RefinedThis(rt) => - if (rt eq from) to else tp + case tp @ SkolemType(binder) => + if (binder eq from) to else tp case tp: NamedType => if (tp.currentSymbol.isStatic) tp - else tp.derivedSelect(substThis(tp.prefix, from, to, theMap)) + else tp.derivedSelect(substSkolem(tp.prefix, from, to, theMap)) case _: ThisType | _: BoundType | NoPrefix => tp case tp: RefinedType => - tp.derivedRefinedType(substThis(tp.parent, from, to, theMap), tp.refinedName, substThis(tp.refinedInfo, from, to, theMap)) + tp.derivedRefinedType(substSkolem(tp.parent, from, to, theMap), tp.refinedName, substSkolem(tp.refinedInfo, from, to, theMap)) case tp: TypeAlias => - tp.derivedTypeAlias(substThis(tp.alias, from, to, theMap)) + tp.derivedTypeAlias(substSkolem(tp.alias, from, to, theMap)) case _ => - (if (theMap != null) theMap else new SubstRefinedThisMap(from, to)) + (if (theMap != null) theMap else new SubstSkolemMap(from, to)) .mapOver(tp) } @@ -222,7 +222,7 @@ trait Substituters { this: Context => case tp: NamedType => if (tp.currentSymbol.isStatic) tp else tp.derivedSelect(substParams(tp.prefix, from, to, theMap)) - case _: ThisType | NoPrefix | _: RefinedThis => + case _: ThisType | NoPrefix | _: SkolemType => tp case tp: RefinedType => tp.derivedRefinedType(substParams(tp.parent, from, to, theMap), tp.refinedName, substParams(tp.refinedInfo, from, to, theMap)) @@ -266,8 +266,8 @@ trait Substituters { this: Context => def apply(tp: Type): Type = substThis(tp, from, to, this) } - final class SubstRefinedThisMap(from: RefinedType, to: Type) extends DeepTypeMap { - def apply(tp: Type): Type = substThis(tp, from, to, this) + final class SubstSkolemMap(from: Type, to: Type) extends DeepTypeMap { + def apply(tp: Type): Type = substSkolem(tp, from, to, this) } final class SubstParamMap(from: ParamType, to: Type) extends DeepTypeMap { diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 613d4b29cb76..c8fbb3d03977 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -600,7 +600,7 @@ object SymDenotations { isTerm && !is(MethodOrLazy, butNot = Label) && !isLocalDummy // def isOverridable: Boolean = !!! need to enforce that classes cannot be redefined - // def isSkolem: Boolean = ??? + def isSkolem: Boolean = name == nme.SKOLEM // ------ access to related symbols --------------------------------- diff --git a/src/dotty/tools/dotc/core/Symbols.scala b/src/dotty/tools/dotc/core/Symbols.scala index 2be97691fe9c..2854d7d2f23a 100644 --- a/src/dotty/tools/dotc/core/Symbols.scala +++ b/src/dotty/tools/dotc/core/Symbols.scala @@ -256,6 +256,9 @@ trait Symbols { this: Context => tparams } + /** Create a new skolem symbol. This is not the same as SkolemType, even thouggh the + * motivation (create a singleton referencing to a type)= is similar. + */ def newSkolem(tp: Type) = newSymbol(defn.RootClass, nme.SKOLEM, SyntheticArtifact | Permanent, tp) def newErrorSymbol(owner: Symbol, name: Name) = diff --git a/src/dotty/tools/dotc/core/TrackingConstraint.scala b/src/dotty/tools/dotc/core/TrackingConstraint.scala new file mode 100644 index 000000000000..fc8a083f400b --- /dev/null +++ b/src/dotty/tools/dotc/core/TrackingConstraint.scala @@ -0,0 +1,480 @@ +package dotty.tools +package dotc +package core + +import Types._, Contexts._, Symbols._, Decorators._ +import util.SimpleMap +import collection.mutable +import printing.{Printer, Showable} +import printing.Texts._ +import config.Config +import config.Printers._ +import collection.immutable.BitSet +import reflect.ClassTag + +object TrackingConstraint { + + /** The type of `Constraint#myMap` */ + type ParamInfo = SimpleMap[PolyType, Array[Type]] + +} + +import TrackingConstraint._ + +/** Constraint over undetermined type parameters + * @param myMap a map from PolyType to arrays. + * Each array contains twice the number of entries as there a type parameters + * in the PolyType. The first half of the array contains the type bounds that constrain the + * polytype's type parameters. The second half might contain type variables that + * track the corresponding parameters, or is left empty (filled with nulls). + * An instantiated type parameter is represented by having its instance type in + * the corresponding array entry. + */ +class TrackingConstraint(private val myMap: ParamInfo, + private val less: Array[BitSet], + private val params: Array[PolyParam]) extends Constraint { + + type This = TrackingConstraint + + assert(less.length == params.length) + + /** A new constraint which is derived from this constraint by adding or replacing + * the entries corresponding to `pt` with `entries`. + */ + private def newConstraint(myMap: ParamInfo, less: Array[BitSet], params: Array[PolyParam])(implicit ctx: Context) : TrackingConstraint = { + val result = new TrackingConstraint(myMap, less, params) + if (Config.checkConstraintsNonCyclic) result.checkNonCyclic() + ctx.runInfo.recordConstraintSize(result, result.myMap.size) + result + } + +// ----------- Basic indices -------------------------------------------------- + + /** The immutable array of constrained polytypes */ + private val polyTypes = new Array[PolyType](myMap.size) + + /** The start positions of parameters of constrained polytypes in `params` and `less` */ + private val polyStart = new Array[Int](myMap.size) + + { + var idx = 0 + var count = 0 + myMap.foreachBinding { (pt, _) => + polyTypes(idx) = pt + polyStart(idx) = count + count += pt.paramNames.length + idx += 1 + } + assert(count == params.length) + } + + /** The index of given polytype `pt` in this constraint, + * or `polyTypes.length` if constraint does not contain `pt`. + */ + private def polyIndex(pt: PolyType): Int = { + var i = 0 + while (i < polyTypes.length && (polyTypes(i) ne pt)) i += 1 + i + } + + /** The index of the first parameter of given polytype `pt` in this constraint */ + private def polyStart(pt: PolyType): Int = this.polyStart.apply(polyIndex(pt)) + + /** The index of `param` in `params` and `less` */ + private def paramIndex(param: PolyParam): Int = { + assert(contains(param.binder)) + polyStart(param.binder) + param.paramNum + } + + /** The number of type parameters in the given entry array */ + private def paramCount(entries: Array[Type]) = entries.length >> 1 + + /** The type variable corresponding to parameter numbered `n`, null if none was created */ + private def typeVar(entries: Array[Type], n: Int): Type = + entries(paramCount(entries) + n) + + def entry(param: PolyParam): Type = { + val entries = myMap(param.binder) + if (entries == null) NoType + else entries(param.paramNum) + } + +// ----------- Contains tests -------------------------------------------------- + + def contains(pt: PolyType): Boolean = polyIndex(pt) < polyTypes.length + + def contains(param: PolyParam): Boolean = { + val entries = myMap(param.binder) + entries != null && entries(param.paramNum).isInstanceOf[TypeBounds] + } + + def contains(tvar: TypeVar): Boolean = { + val origin = tvar.origin + val entries = myMap(origin.binder) + val pnum = origin.paramNum + entries != null && isBounds(entries(pnum)) && (typeVar(entries, pnum) eq tvar) + } + + private def isBounds(tp: Type) = tp.isInstanceOf[TypeBounds] + +// ---------- Dependency handling ---------------------------------------------- + + private def upperBits(less: Array[BitSet], i: Int): BitSet = less(i) + + private def lowerBits(less: Array[BitSet], i: Int): BitSet = + (BitSet() /: less.indices) ((bits, j) => if (less(j)(i)) bits + j else bits) + + private def minUpperBits(less: Array[BitSet], i: Int): BitSet = { + val all = upperBits(less, i) + all.filterNot(j => all.exists(k => less(k)(j))) + } + + private def minLowerBits(less: Array[BitSet], i: Int): BitSet = { + val all = lowerBits(less, i) + all.filterNot(j => all.exists(k => less(j)(k))) + } + + private def overParams(op: (Array[BitSet], Int) => BitSet): PolyParam => List[PolyParam] = param => + op(less, paramIndex(param)).toList.map(params).filter(contains) + + val allUpper = overParams(upperBits) + val allLower = overParams(lowerBits) + val minUpper = overParams(minUpperBits) + val minLower = overParams(minLowerBits) + + def upper(param: PolyParam): List[PolyParam] = allUpper(param) + def lower(param: PolyParam): List[PolyParam] = allLower(param) + + def exclusiveLower(param: PolyParam, butNot: PolyParam): List[PolyParam] = { + val excluded = lowerBits(less, paramIndex(butNot)) + overParams(lowerBits(_, _) &~ excluded)(param) + } + + def exclusiveUpper(param: PolyParam, butNot: PolyParam): List[PolyParam] = { + val excluded = upperBits(less, paramIndex(butNot)) + overParams(upperBits(_, _) &~ excluded)(param) + } + +// ---------- Info related to PolyParams ------------------------------------------- + + def isLess(param1: PolyParam, param2: PolyParam): Boolean = + less(paramIndex(param1))(paramIndex(param2)) + + def nonParamBounds(param: PolyParam): TypeBounds = + entry(param).asInstanceOf[TypeBounds] + + def fullLowerBound(param: PolyParam)(implicit ctx: Context): Type = + (nonParamBounds(param).lo /: minLower(param))(_ | _) + + def fullUpperBound(param: PolyParam)(implicit ctx: Context): Type = + (nonParamBounds(param).hi /: minUpper(param))(_ & _) + + def fullBounds(param: PolyParam)(implicit ctx: Context): TypeBounds = + nonParamBounds(param).derivedTypeBounds(fullLowerBound(param), fullUpperBound(param)) + + def typeVarOfParam(param: PolyParam): Type = { + val entries = myMap(param.binder) + if (entries == null) NoType + else { + val tvar = typeVar(entries, param.paramNum) + if (tvar != null) tvar else NoType + } + } + +// ---------- Adding PolyTypes -------------------------------------------------- + + /** The bound type `tp` without dependent parameters + * NoType if type consists only of dependent parameters. + * @param seenFromBelow If true, `bound` is an upper bound, else a lower bound. + */ + private def stripParams(tp: Type, handleParam: (PolyParam, Boolean) => Type, + seenFromBelow: Boolean)(implicit ctx: Context): Type = tp match { + case tp: PolyParam => + handleParam(tp, seenFromBelow) + case tp: AndOrType if seenFromBelow == tp.isAnd => + val tp1 = stripParams(tp.tp1, handleParam, seenFromBelow) + val tp2 = stripParams(tp.tp2, handleParam, seenFromBelow) + if (tp1.exists) + if (tp2.exists) tp.derivedAndOrType(tp1, tp2) + else tp1 + else tp2 + case _ => + tp + } + + /** The bound type `tp` without dependent parameters. + * A top or bottom type if type consists only of dependent parameters. + * @param seenFromBelow If true, `bound` is an upper bound, else a lower bound. + */ + private def nonParamType(tp: Type, handleParam: (PolyParam, Boolean) => Type, + seenFromBelow: Boolean)(implicit ctx: Context): Type = + stripParams(tp, handleParam, seenFromBelow) + .orElse(if (seenFromBelow) defn.AnyType else defn.NothingType) + + /** The bounds of `tp1` without dependent parameters. + * @pre `tp` is a TypeBounds type. + */ + private def nonParamBounds(tp: Type, handleParam: (PolyParam, Boolean) => Type)(implicit ctx: Context): Type = tp match { + case tp @ TypeBounds(lo, hi) => + tp.derivedTypeBounds( + nonParamType(lo, handleParam, seenFromBelow = false), + nonParamType(hi, handleParam, seenFromBelow = true)) + } + + def add(poly: PolyType, tvars: List[TypeVar])(implicit ctx: Context): This = { + assert(!contains(poly)) + val nparams = poly.paramNames.length + val entries1 = new Array[Type](nparams * 2) + poly.paramBounds.copyToArray(entries1, 0) + tvars.copyToArray(entries1, nparams) + val is = poly.paramBounds.indices + val newParams = is.map(PolyParam(poly, _)) + val params1 = params ++ newParams + var less1 = less ++ is.map(Function.const(BitSet.empty)) + for (i <- is) { + def handleParam(param: PolyParam, seenFromBelow: Boolean): Type = { + def record(paramIdx: Int): Type = { + less1 = + if (seenFromBelow) updatedLess(less1, nparams + i, paramIdx) + else updatedLess(less1, paramIdx, nparams + i) + NoType + } + if (param.binder eq poly) record(nparams + param.paramNum) + else if (contains(param.binder)) record(paramIndex(param)) + else param + } + entries1(i) = nonParamBounds(entries1(i), handleParam) + } + newConstraint(myMap.updated(poly, entries1), less1, params1) + } + +// ---------- Updates ------------------------------------------------------------ + + /** An updated partial order matrix that incorporates `less` and also reflects that `param` relates + * to `p2` wrt <:< if `inOrder` is true, `>:>` otherwise. + */ + private def updatedLess(less: Array[BitSet], i1: Int, i2: Int): Array[BitSet] = { + if (i1 == i2 || less(i1)(i2)) less + else { + val result = less.clone + val newUpper = upperBits(less, i2) + i2 + def update(j: Int) = { + result(j) |= newUpper + assert(!result(j)(j)) + } + update(i1) + lowerBits(less, i1).foreach(update) + result + } + } + + def addLess(p1: PolyParam, p2: PolyParam)(implicit ctx: Context): This = { + val less1 = updatedLess(less, paramIndex(p1), paramIndex(p2)) + if (less1 eq less) this else newConstraint(myMap, less1, params) + } + + def updateEntry(param: PolyParam, tp: Type)(implicit ctx: Context): This = { + val entries = myMap(param.binder) + val entry = entries(param.paramNum) + if (entry eq tp) this + else { + val entries1 = entries.clone + entries1(param.paramNum) = tp + newConstraint(myMap.updated(param.binder, entries1), less, params) + } + } + + def unify(p1: PolyParam, p2: PolyParam)(implicit ctx: Context): This = { + val p1Bounds = (nonParamBounds(p1) & nonParamBounds(p2)).substParam(p2, p1) + updateEntry(p1, p1Bounds).replace(p2, p1) + } + + def narrowBound(param: PolyParam, bound: Type, isUpper: Boolean)(implicit ctx: Context): This = { + val oldBounds @ TypeBounds(lo, hi) = nonParamBounds(param) + val newBounds = + if (isUpper) oldBounds.derivedTypeBounds(lo, hi & bound) + else oldBounds.derivedTypeBounds(lo | bound, hi) + if (newBounds eq oldBounds) this + else updateEntry(param, newBounds) + } + +// ---------- Removals ------------------------------------------------------------ + + /** Drop parameter `PolyParam(poly, n)` from `bounds`, + * replacing with Nothing in the lower bound and by `Any` in the upper bound. + */ + private def dropParamIn(bounds: TypeBounds, poly: PolyType, n: Int)(implicit ctx: Context): TypeBounds = { + def drop(tp: Type): Type = tp match { + case tp: AndOrType => + val tp1 = drop(tp.tp1) + val tp2 = drop(tp.tp2) + if (!tp1.exists) tp2 + else if (!tp2.exists) tp1 + else tp + case PolyParam(`poly`, `n`) => NoType + case _ => tp + } + def approx(tp: Type, limit: Type): Type = { + val tp1 = drop(tp) + if (tp1.exists || !tp.exists) tp1 else limit + } + bounds.derivedTypeBounds( + approx(bounds.lo, defn.NothingType), approx(bounds.hi, defn.AnyType)) + } + + def replace(param: PolyParam, tp: Type)(implicit ctx: Context): TrackingConstraint = { + val replacement = tp.dealias.stripTypeVar + + def subst(poly: PolyType, entries: Array[Type]) = { + var result = entries + var i = 0 + while (i < paramCount(entries)) { + entries(i) match { + case oldBounds: TypeBounds => + val newBounds = oldBounds.substParam(param, replacement).asInstanceOf[TypeBounds] + if (oldBounds ne newBounds) { + if (result eq entries) result = entries.clone + result(i) = dropParamIn(newBounds, poly, i) + } + case _ => + } + i += 1 + } + result + } + + if (param == replacement) this + else { + assert(replacement.isValueType) + val pt = param.binder + val constr1 = if (isRemovable(pt, param.paramNum)) remove(pt) else updateEntry(param, replacement) + newConstraint(constr1.myMap mapValues subst, constr1.less, constr1.params) + } + } + + def remove(pt: PolyType)(implicit ctx: Context): This = { + val start = polyStart(pt) + val skipped = pt.paramNames.length + + def shrinkSet(bits: BitSet): BitSet = + (BitSet() /: bits) ((res, i) => + if (i < start) res + i + else if (i < start + skipped) res + else res + (i - skipped)) + def shrinkArray[T: ClassTag](src: Array[T]) = { + val dst = new Array[T](src.length - skipped) + Array.copy(src, 0, dst, 0, start) + Array.copy(src, start + skipped, dst, start, dst.length - start) + dst + } + newConstraint( + myMap = myMap remove pt, + less = shrinkArray(less).map(shrinkSet(_)), + params = shrinkArray(params)) + } + + def isRemovable(pt: PolyType, removedParam: Int = -1): Boolean = { + val entries = myMap(pt) + var noneLeft = true + var i = paramCount(entries) + while (noneLeft && i > 0) { + i -= 1 + if (i != removedParam && isBounds(entries(i))) noneLeft = false + else typeVar(entries, i) match { + case tv: TypeVar => + if (!tv.inst.exists) noneLeft = false // need to keep line around to compute instType + case _ => + } + } + noneLeft + } + +// ---------- Exploration -------------------------------------------------------- + + def domainPolys: List[PolyType] = polyTypes.toList + + def domainParams: List[PolyParam] = params.toList.filter(contains) + + def forallParams(p: PolyParam => Boolean): Boolean = { + myMap.foreachBinding { (poly, entries) => + for (i <- 0 until paramCount(entries)) + if (isBounds(entries(i)) && !p(PolyParam(poly, i))) return false + } + true + } + + def foreachTypeVar(op: TypeVar => Unit): Unit = + myMap.foreachBinding { (poly, entries) => + for (i <- 0 until paramCount(entries)) { + typeVar(entries, i) match { + case tv: TypeVar if !tv.inst.exists => op(tv) + case _ => + } + } + } + + private var myUninstVars: mutable.ArrayBuffer[TypeVar] = _ + + /** The uninstantiated typevars of this constraint */ + def uninstVars: collection.Seq[TypeVar] = { + if (myUninstVars == null) { + myUninstVars = new mutable.ArrayBuffer[TypeVar] + myMap.foreachBinding { (poly, entries) => + for (i <- 0 until paramCount(entries)) { + typeVar(entries, i) match { + case tv: TypeVar if !tv.inst.exists && isBounds(entries(i)) => myUninstVars += tv + case _ => + } + } + } + } + myUninstVars + } + +// ---------- Cyclic checking ------------------------------------------- + + private def checkNonCyclic(idx: Int)(implicit ctx: Context): Unit = + assert(!less(idx)(idx), i"cyclic constraint involving ${params(idx)}") + + def checkNonCyclic()(implicit ctx: Context): Unit = + for (i <- params.indices) checkNonCyclic(i) + +// ---------- toText ----------------------------------------------------- + + override def toText(printer: Printer): Text = { + def entryText(tp: Type) = tp match { + case tp: TypeBounds => + tp.toText(printer) + case _ => + " := " ~ tp.toText(printer) + } + val indent = 3 + val header: Text = "Constraint(" + val uninstVarsText = " uninstVars = " ~ + Text(uninstVars map (_.toText(printer)), ", ") ~ ";" + val constrainedText = + " constrained types = " ~ Text(domainPolys map (_.toText(printer)), ", ") + val boundsText = + " bounds = " ~ { + val assocs = + for (param <- domainParams) + yield (" " * indent) ~ param.toText(printer) ~ entryText(entry(param)) + Text(assocs, "\n") + } + val orderingText = + " ordering = " ~ { + val deps = + for { + param <- domainParams + ups = minUpper(param) + if ups.nonEmpty + } + yield + (" " * indent) ~ param.toText(printer) ~ " <: " ~ + Text(ups.map(_.toText(printer)), ", ") + Text(deps, "\n") + } + Text.lines(List(header, uninstVarsText, constrainedText, boundsText, orderingText, ")")) + } +} + diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index 6443c5054136..e59c6095945b 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -372,6 +372,29 @@ class TypeApplications(val self: Type) extends AnyVal { case JavaArrayType(elemtp) => elemtp case _ => firstBaseArgInfo(defn.SeqClass) } + + def containsSkolemType(target: Type)(implicit ctx: Context): Boolean = { + def recur(tp: Type): Boolean = tp.stripTypeVar match { + case SkolemType(tp) => + tp eq target + case tp: NamedType => + tp.info match { + case TypeAlias(alias) => recur(alias) + case _ => !tp.symbol.isStatic && recur(tp.prefix) + } + case tp: RefinedType => + recur(tp.refinedInfo) || recur(tp.parent) + case tp: TypeBounds => + recur(tp.lo) || recur(tp.hi) + case tp: AnnotatedType => + recur(tp.underlying) + case tp: AndOrType => + recur(tp.tp1) || recur(tp.tp2) + case _ => + false + } + recur(self) + } /** Given a type alias * @@ -407,16 +430,15 @@ class TypeApplications(val self: Type) extends AnyVal { if (bsyms.isEmpty) { val correspondingNames = correspondingParamName.values.toSet - def replacements(rt: RefinedType): List[Type] = + def replacements(rt: RefinedType): List[Type] = for (sym <- boundSyms) - yield TypeRef(RefinedThis(rt), correspondingParamName(sym)) + yield TypeRef(SkolemType(rt), correspondingParamName(sym)) def rewrite(tp: Type): Type = tp match { case tp @ RefinedType(parent, name: TypeName) => if (correspondingNames contains name) rewrite(parent) else RefinedType( - rewrite(parent), - name, + rewrite(parent), name, rt => tp.refinedInfo.subst(boundSyms, replacements(rt))) case tp => tp @@ -453,7 +475,7 @@ class TypeApplications(val self: Type) extends AnyVal { val lambda = defn.lambdaTrait(boundSyms.map(_.variance)) val substitutedRHS = (rt: RefinedType) => { val argRefs = boundSyms.indices.toList.map(i => - RefinedThis(rt).select(tpnme.lambdaArgName(i))) + SkolemType(rt).select(tpnme.lambdaArgName(i))) tp.subst(boundSyms, argRefs).bounds.withVariance(1) } val res = RefinedType(lambda.typeRef, tpnme.Apply, substitutedRHS) @@ -486,7 +508,7 @@ class TypeApplications(val self: Type) extends AnyVal { * LambdaXYZ { type Apply = B[$hkArg$0, ..., $hkArg$n] } * { type $hkArg$0 = T1; ...; type $hkArg$n = Tn } * - * satisfies predicate `p`. Try base types in the order of ther occurrence in `baseClasses`. + * satisfies predicate `p`. Try base types in the order of their occurrence in `baseClasses`. * A type parameter matches a varianve V if it has V as its variance or if V == 0. */ def testLifted(tparams: List[Symbol], p: Type => Boolean)(implicit ctx: Context): Boolean = { diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index cae4500e465b..9d6acee71496 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -16,7 +16,7 @@ import scala.util.control.NonFatal /** Provides methods to compare types. */ -class TypeComparer(initctx: Context) extends DotClass { +class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling with Skolemization { implicit val ctx: Context = initctx val state = ctx.typerState @@ -25,23 +25,9 @@ class TypeComparer(initctx: Context) extends DotClass { private var pendingSubTypes: mutable.Set[(Type, Type)] = null private var recCount = 0 - /** If the constraint is frozen we cannot add new bounds to the constraint. */ - protected var frozenConstraint = false - - /** If the constraint is ignored, subtype checks only take into account - * declared bounds of PolyParams. Used when forming unions and intersectons - * of constraint bounds - */ - protected var ignoreConstraint = false - - /** Compare a solution of the constraint instead of the constrained parameters. - * The solution maps every parameter to its lower bound. - */ - protected var solvedConstraint = false - private var needsGc = false - /** Is a subtype check in course? In that case we may not + /** Is a subtype check in progress? In that case we may not * permanently instantiate type variables, because the corresponding * constraint might still be retracted and the instantiation should * then be reversed. @@ -64,6 +50,7 @@ class TypeComparer(initctx: Context) extends DotClass { private var myNullClass: ClassSymbol = null private var myObjectClass: ClassSymbol = null private var myAnyType: TypeRef = null + private var myNothingType: TypeRef = null def AnyClass = { if (myAnyClass == null) myAnyClass = defn.AnyClass @@ -85,294 +72,23 @@ class TypeComparer(initctx: Context) extends DotClass { if (myAnyType == null) myAnyType = AnyClass.typeRef myAnyType } - - // Constraint handling - - /** Map that approximates each param in constraint by its lower bound. - * Currently only used for diagnostics. - */ - val approxParams = new TypeMap { - def apply(tp: Type): Type = tp.stripTypeVar match { - case tp: PolyParam if constraint contains tp => - this(constraint.bounds(tp).lo) - case tp => - mapOver(tp) - } - } - - /** If `param` is contained in constraint, test whether its - * bounds are non-empty. Otherwise return `true`. - */ - private def checkBounds(param: PolyParam): Boolean = constraint.at(param) match { - case TypeBounds(lo, hi) => - if (Stats.monitored) Stats.record("checkBounds") - isSubType(lo, hi) - case _ => true - } - - /** Test validity of constraint for parameter `changed` and of all - * parameters that depend on it. - */ - private def propagate(changed: PolyParam): Boolean = - if (Config.trackConstrDeps) - checkBounds(changed) && - propagate(constraint.dependentParams(changed) - changed, Set(changed)) - else - constraint forallParams checkBounds - - /** Ensure validity of constraints for parameters `params` and of all - * parameters that depend on them and that have not been tested - * now or before. If `trackConstrDeps` is not set, do this for all - * parameters in the constraint. - * @param seen the set of parameters that have been tested before. - */ - private def propagate(params: Set[PolyParam], seen: Set[PolyParam]): Boolean = - params.isEmpty || - (params forall checkBounds) && { - val seen1 = seen ++ params - val nextParams = (Set[PolyParam]() /: params) { (ps, p) => - ps ++ (constraint.dependentParams(p) -- seen1) - } - propagate(nextParams, seen1) - } - - /** Check whether the lower bounds of all parameters in this - * constraint are a solution to the constraint. - * As an optimization, when `trackConstrDeps` is set, we - * only test that the solutions satisfy the constraints `changed` - * and all parameters that depend on it. - */ - def isSatisfiable(changed: PolyParam): Boolean = { - val saved = solvedConstraint - solvedConstraint = true - try - if (Config.trackConstrDeps) propagate(changed) - else - constraint.forallParams { param => - checkBounds(param) || { - val TypeBounds(lo, hi) = constraint.bounds(param) - ctx.log(i"sub fail $lo <:< $hi") - ctx.log(i"approximated = ${approxParams(lo)} <:< ${approxParams(hi)}") - false - } - } - finally solvedConstraint = saved - } - - /** Update constraint for `param` to `bounds`, check that - * new constraint is still satisfiable. - */ - private def updateConstraint(param: PolyParam, bounds: TypeBounds): Boolean = { - val saved = constraint - constraint = constraint.updated(param, bounds) - if (propagate(param)) { - if (isSatisfiable(param)) return true - ctx.log(i"SAT $constraint produced by $param $bounds is not satisfiable") - } - constraint = saved // don't leave the constraint in unsatisfiable state - false - } - - private def addConstraint1(param: PolyParam, bound: Type, fromBelow: Boolean): Boolean = { - val oldBounds = constraint.bounds(param) - assert(!bound.isInstanceOf[TypeVar]) - val saved = ignoreConstraint - ignoreConstraint = true - val newBounds = - try - if (fromBelow) oldBounds.derivedTypeBounds(oldBounds.lo | bound, oldBounds.hi) - else oldBounds.derivedTypeBounds(oldBounds.lo, oldBounds.hi & bound) - finally ignoreConstraint = saved - val res = - (param == bound) || (oldBounds eq newBounds) || updateConstraint(param, newBounds) - constr.println(s"added1 constraint $param ${if (fromBelow) ">:" else "<:"} $bound = $res") - if (res) constr.println(constraint.show) - res - } - - /** Make p2 = p1, transfer all bounds of p2 to p1 */ - private def unify(p1: PolyParam, p2: PolyParam): Boolean = { - constr.println(s"unifying $p1 $p2") - val constraint1 = constraint.unify(p1, p2) - val bounds = constraint1.bounds(p1) - isSubType(bounds.lo, bounds.hi) && { constraint = constraint1; true } - } - - /** If current constraint set is not frozen, add the constraint - * - * param >: bound if fromBelow is true - * param <: bound otherwise - * - * to the bounds of `param`. If `bound` is itself a constrained parameter, also - * add the dual constraint to `bound`. - * @pre `param` is in the constraint's domain - * @return Whether the augmented constraint is still satisfiable. - */ - def addConstraint(param: PolyParam, bound0: Type, fromBelow: Boolean): Boolean = { - assert(!frozenConstraint) - val bound = bound0.dealias.stripTypeVar - def description = s"${param.show} ${if (fromBelow) ">:>" else "<:<"} ${bound.show} (${bound.getClass}) to ${constraint.show}" - constr.println(s"adding $description") - val res = bound match { - case bound: PolyParam if constraint contains bound => - val TypeBounds(lo, hi) = constraint.bounds(bound) - if (lo eq hi) - addConstraint(param, lo, fromBelow) - else if (param == bound) - true - else if (fromBelow && param.occursIn(lo, fromBelow = true)) - unify(param, bound) - else if (!fromBelow && param.occursIn(hi, fromBelow = false)) - unify(bound, param) - else - addConstraint1(param, bound, fromBelow) && - addConstraint1(bound, param, !fromBelow) - case bound: AndOrType if fromBelow != bound.isAnd => - addConstraint(param, bound.tp1, fromBelow) && - addConstraint(param, bound.tp2, fromBelow) - case bound: WildcardType => - true - case bound => // !!! remove to keep the originals - addConstraint1(param, bound, fromBelow) - } - constr.println(s"added $description = ${constraint.show}") - res - } - - def isConstrained(param: PolyParam): Boolean = - !frozenConstraint && !solvedConstraint && (constraint contains param) - - /** Solve constraint set for given type parameter `param`. - * If `fromBelow` is true the parameter is approximated by its lower bound, - * otherwise it is approximated by its upper bound. However, any occurrences - * of the parameter in a refinement somewhere in the bound are removed. - * (Such occurrences can arise for F-bounded types). - * The constraint is left unchanged. - * @return the instantiating type - * @pre `param` is in the constraint's domain. - */ - def approximation(param: PolyParam, fromBelow: Boolean): Type = { - val avoidParam = new TypeMap { - override def stopAtStatic = true - def apply(tp: Type) = mapOver { - tp match { - case tp: RefinedType if param occursIn tp.refinedInfo => tp.parent - case _ => tp - } - } - } - val bounds = constraint.bounds(param) - val bound = if (fromBelow) bounds.lo else bounds.hi - val inst = avoidParam(bound) - typr.println(s"approx ${param.show}, from below = $fromBelow, bound = ${bound.show}, inst = ${inst.show}") - inst - } - - // Keeping track of seen refinements - - /** A map from refined names to the refined types in which they occur. - * During the subtype check involving the parent of a refined type, - * the refined name is stored in the map, so that the outermost - * refinements can be retrieved when interpreting a reference to the name. - * The name is associated with a pair of refinements. If the refinedInfo is - * skipped in sub- and super-type at the same time (first clause of - * `compareRefined`, both refinements are stored. If the name only appears - * as a refinement in the sub- or -super-type, the refinement type is stored - * twice as both elements of the pair. - */ - protected var pendingRefinedBases: SimpleMap[Name, Set[(RefinedType, RefinedType)]] - = SimpleMap.Empty - - /** Add pending name to `pendingRefinedBases`. */ - private def addPendingName(name: Name, rt1: RefinedType, rt2: RefinedType) = { - var s = pendingRefinedBases(name) - if (s == null) s = Set() - pendingRefinedBases = pendingRefinedBases.updated(name, s + ((rt1, rt2))) - } - - /** Given a selection of qualifier `qual` with given `name`, return a refined type - * that refines `qual`, or if that fails return `qual` itself. - * @param considerBoth If true consider both lower and upper base of `name` when - * checking for refinement (but always return the lower one) - * @see Type#refines - */ - private def rebaseQual(qual: Type, name: Name, considerBoth: Boolean = false): Type = { - val bases = pendingRefinedBases(name) - if (bases == null) qual - else bases.find { - case (tp1, tp2) => - (tp1 refines qual) || considerBoth && (tp1 ne tp2) && (tp2 refines qual) - } match { - case Some((base1, _)) => base1 - case _ => qual - } - } - - private def narrowRefined(tp: Type): Type = tp match { - case tp: RefinedType => RefinedThis(tp) - case _ => tp - } - - /** If the prefix of a named type is `this` (i.e. an instance of type - * `ThisType` or `RefinedThis`), and there is a refinement type R that - * "refines" (transitively contains as its parent) a class reference - * or refinement corresponding to the prefix, return the named type where - * the prefix is replaced by `RefinedThis(R)`. Otherwise return the named type itself. - */ - private def rebase(tp: NamedType): Type = { - def rebaseFrom(prefix: Type): Type = { - rebaseQual(prefix, tp.name, considerBoth = true) match { - case rt: RefinedType if rt ne prefix => - tp.derivedSelect(RefinedThis(rt)).dealias // dealias to short-circuit cycles spanning type aliases or LazyRefs - case _ => tp - } - } - tp.prefix match { - case RefinedThis(rt) => rebaseFrom(rt) - case pre: ThisType => rebaseFrom(pre.cls.info) - case _ => tp - } - } - - /** If the given refined type is refined further, return the member - * of the refiend name relative to the refining base, otherwise return - * `refinedInfo`. - * TODO: Figure out why cannot simply write - * - * rebaseQual(rt, rt.refinedName).member(rt.refinedName).info - * - * (too much forcing, probably). - */ - def normalizedInfo(rt: RefinedType) = { - val base = rebaseQual(rt, rt.refinedName) - if (base eq rt) rt.refinedInfo else base.member(rt.refinedName).info + def NothingType = { + if (myNothingType == null) myNothingType = NothingClass.typeRef + myNothingType } // Subtype testing `<:<` def topLevelSubType(tp1: Type, tp2: Type): Boolean = { if (tp2 eq NoType) return false - if ((tp2 eq tp1) || - (tp2 eq WildcardType) || - (tp2 eq AnyType) && tp1.isValueType) return true - isSubType(tp1, tp2) - } - - def isSubTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = { - val saved = frozenConstraint - frozenConstraint = true + if ((tp2 eq tp1) || (tp2 eq WildcardType)) return true try isSubType(tp1, tp2) - finally frozenConstraint = saved + finally + if (Config.checkConstraintsSatisfiable) + assert(isSatisfiable, constraint.show) } - def isNonBottomSubType(tp1: Type, tp2: Type): Boolean = - !(tp2 isRef NothingClass) && isSubType(tp1, tp2) - - private def traceInfo(tp1: Type, tp2: Type) = - s"${tp1.show} <:< ${tp2.show}" + - (if (ctx.settings.verbose.value) s" ${tp1.getClass} ${tp2.getClass}${if (frozenConstraint) " frozen" else ""}" else "") - - def isSubType(tp1: Type, tp2: Type): Boolean = /*>|>*/ ctx.traceIndented(s"isSubType ${traceInfo(tp1, tp2)}", subtyping) /*<|<*/ { + protected def isSubType(tp1: Type, tp2: Type): Boolean = ctx.traceIndented(s"isSubType ${traceInfo(tp1, tp2)}", subtyping) /*<|<*/ { if (tp2 eq NoType) false else if (tp1 eq tp2) true else { @@ -385,36 +101,15 @@ class TypeComparer(initctx: Context) extends DotClass { else monitoredIsSubType(tp1, tp2) recCount = recCount - 1 if (!result) constraint = saved - else if (recCount == 0 && needsGc) state.gc() - - def recordStatistics = { - // Stats.record(s"isSubType ${tp1.show} <:< ${tp2.show}") - totalCount += 1 - if (result) successCount += 1 else successCount = savedSuccessCount - if (recCount == 0) { - Stats.record("successful subType", successCount) - Stats.record("total subType", totalCount) - successCount = 0 - totalCount = 0 - } + else if (recCount == 0 && needsGc) { + state.gc() + needsGc = false } - if (Stats.monitored) recordStatistics - + if (Stats.monitored) recordStatistics(result, savedSuccessCount) result } catch { case NonFatal(ex) => - def showState = { - println(disambiguated(implicit ctx => s"assertion failure for ${tp1.show} <:< ${tp2.show}, frozen = $frozenConstraint")) - def explainPoly(tp: Type) = tp match { - case tp: PolyParam => println(s"polyparam ${tp.show} found in ${tp.binder.show}") - case tp: TypeRef if tp.symbol.exists => println(s"typeref ${tp.show} found in ${tp.symbol.owner.show}") - case tp: TypeVar => println(s"typevar ${tp.show}, origin = ${tp.origin}") - case _ => println(s"${tp.show} is a ${tp.getClass}") - } - explainPoly(tp1) - explainPoly(tp2) - } - if (ex.isInstanceOf[AssertionError]) showState + if (ex.isInstanceOf[AssertionError]) showGoal(tp1, tp2) recCount -= 1 constraint = saved successCount = savedSuccessCount @@ -423,7 +118,7 @@ class TypeComparer(initctx: Context) extends DotClass { } } - def monitoredIsSubType(tp1: Type, tp2: Type) = { + private def monitoredIsSubType(tp1: Type, tp2: Type) = { if (pendingSubTypes == null) { pendingSubTypes = new mutable.HashSet[(Type, Type)] ctx.log(s"!!! deep subtype recursion involving ${tp1.show} <:< ${tp2.show}, constraint = ${state.constraint.show}") @@ -443,141 +138,141 @@ class TypeComparer(initctx: Context) extends DotClass { } } - def firstTry(tp1: Type, tp2: Type): Boolean = { - tp2 match { - case tp2: NamedType => - def isHKSubType = tp2.name == tpnme.Apply && { - val lambda2 = tp2.prefix.LambdaClass(forcing = true) - lambda2.exists && !tp1.isLambda && - tp1.testLifted(lambda2.typeParams, isSubType(_, tp2.prefix)) - } - def compareNamed = { - implicit val ctx: Context = this.ctx // Dotty deviation: implicits need explicit type - tp1 match { - case tp1: NamedType => - val sym1 = tp1.symbol - (if ((sym1 ne NoSymbol) && (sym1 eq tp2.symbol)) ( - ctx.erasedTypes - || sym1.isStaticOwner - || { // Implements: A # X <: B # X - // if either A =:= B (i.e. A <: B and B <: A), or the following three conditions hold: - // 1. X is a class type, - // 2. B is a class type without abstract type members. - // 3. A <: B. - // Dealiasing is taken care of elsewhere. - val pre1 = tp1.prefix - val pre2 = tp2.prefix - ( isSameType(pre1, pre2) - || sym1.isClass - && pre2.classSymbol.exists - && pre2.abstractTypeMembers.isEmpty - && isSubType(pre1, pre2) - ) - } - ) - else (tp1.name eq tp2.name) && isSameType(tp1.prefix, tp2.prefix) - ) || isHKSubType || secondTryNamed(tp1, tp2) - case tp1: ThisType if tp1.cls eq tp2.symbol.moduleClass => - isSubType(tp1.cls.owner.thisType, tp2.prefix) - case _ => - isHKSubType || secondTry(tp1, tp2) - } + private def firstTry(tp1: Type, tp2: Type): Boolean = tp2 match { + case tp2: NamedType => + def compareAlias(info1: Type) = tp2.info match { + case info2: TypeAlias => isSubType(tp1, info2.alias) + case _ => info1 match { + case info1: TypeAlias => isSubType(info1.alias, tp2) + case NoType => secondTry(tp1, tp2) + case _ => thirdTryNamed(tp1, tp2) } - compareNamed - case tp2: ProtoType => - isMatchedByProto(tp2, tp1) - case tp2: PolyParam => - def comparePolyParam = - tp2 == tp1 || { - if (solvedConstraint && (constraint contains tp2)) isSubType(tp1, bounds(tp2).lo) - else - isSubTypeWhenFrozen(tp1, bounds(tp2).lo) || { - if (isConstrained(tp2)) addConstraint(tp2, tp1.widenExpr, fromBelow = true) - else (ctx.mode is Mode.TypevarsMissContext) || secondTry(tp1, tp2) + } + def compareNamed = { + implicit val ctx: Context = this.ctx // Dotty deviation: implicits need explicit type + tp1 match { + case tp1: NamedType => + val sym1 = tp1.symbol + (if ((sym1 ne NoSymbol) && (sym1 eq tp2.symbol)) + ctx.erasedTypes || sym1.isStaticOwner || + { // Implements: A # X <: B # X + // if either A =:= B (i.e. A <: B and B <: A), or the following three conditions hold: + // 1. X is a class type, + // 2. B is a class type without abstract type members. + // 3. A <: B. + // Dealiasing is taken care of elsewhere. + val pre1 = tp1.prefix + val pre2 = tp2.prefix + isSameType(pre1, pre2) || + sym1.isClass && + pre2.classSymbol.exists && + pre2.abstractTypeMembers.isEmpty && + isSubType(pre1, pre2) } - } - comparePolyParam - case tp2: BoundType => - tp2 == tp1 || secondTry(tp1, tp2) - case tp2: TypeVar => - isSubType(tp1, tp2.underlying) - case tp2: WildcardType => - def compareWild = tp2.optBounds match { - case TypeBounds(_, hi) => isSubType(tp1, hi) - case NoType => true + else + (tp1.name eq tp2.name) && + isSameType(tp1.prefix, tp2.prefix) && + (tp1.signature == tp2.signature) && + !tp1.isInstanceOf[WithFixedSym] && + !tp2.isInstanceOf[WithFixedSym] + ) || + compareHK(tp1, tp2, inOrder = true) || + compareHK(tp2, tp1, inOrder = false) || + compareAlias(tp1.info) + case _ => + compareHK(tp2, tp1, inOrder = false) || + compareAlias(NoType) } - compareWild - case tp2: LazyRef => - isSubType(tp1, tp2.ref) - case tp2: AnnotatedType => - isSubType(tp1, tp2.tpe) // todo: refine? - case tp2: ThisType => + } + compareNamed + case tp2: ProtoType => + isMatchedByProto(tp2, tp1) + case tp2: BoundType => + tp2 == tp1 || secondTry(tp1, tp2) + case tp2: TypeVar => + isSubType(tp1, tp2.underlying) + case tp2: WildcardType => + def compareWild = tp2.optBounds match { + case TypeBounds(_, hi) => isSubType(tp1, hi) + case NoType => true + } + compareWild + case tp2: LazyRef => + isSubType(tp1, tp2.ref) + case tp2: AnnotatedType => + isSubType(tp1, tp2.tpe) // todo: refine? + case tp2: ThisType => + def compareThis = { + val cls2 = tp2.cls tp1 match { case tp1: ThisType => // We treat two prefixes A.this, B.this as equivalent if // A's selftype derives from B and B's selftype derives from A. - tp1.cls.classInfo.selfType.derivesFrom(tp2.cls) && - tp2.cls.classInfo.selfType.derivesFrom(tp1.cls) + val cls1 = tp1.cls + cls1.classInfo.selfType.derivesFrom(cls2) && + cls2.classInfo.selfType.derivesFrom(cls1) + case tp1: TermRef if tp2.cls eq tp1.symbol.moduleClass => + isSubType(tp1.prefix, cls2.owner.thisType) case _ => secondTry(tp1, tp2) } - case tp2: SuperType => - tp1 match { - case tp1: SuperType => - isSubType(tp1.thistpe, tp2.thistpe) && - isSameType(tp1.supertpe, tp2.supertpe) - case _ => - secondTry(tp1, tp2) - } - case AndType(tp21, tp22) => - isSubType(tp1, tp21) && isSubType(tp1, tp22) - case ErrorType => - true - case _ => - secondTry(tp1, tp2) - } + } + compareThis + case tp2: SuperType => + def compareSuper = tp1 match { + case tp1: SuperType => + isSubType(tp1.thistpe, tp2.thistpe) && + isSameType(tp1.supertpe, tp2.supertpe) + case _ => + secondTry(tp1, tp2) + } + compareSuper + case AndType(tp21, tp22) => + isSubType(tp1, tp21) && isSubType(tp1, tp22) + case ErrorType => + true + case _ => + secondTry(tp1, tp2) } - def secondTry(tp1: Type, tp2: Type): Boolean = tp1 match { + private def secondTry(tp1: Type, tp2: Type): Boolean = tp1 match { case tp1: NamedType => - tp2 match { - case tp2: ThisType if tp2.cls eq tp1.symbol.moduleClass => - isSubType(tp1.prefix, tp2.cls.owner.thisType) - case _ => - secondTryNamed(tp1, tp2) + tp1.info match { + case info1: TypeAlias => isSubType(info1.alias, tp2) + case _ => compareHK(tp1, tp2, inOrder = true) || thirdTry(tp1, tp2) + // Note: If we change the order here, doing compareHK first and following aliases second, + // we get a -Ycheck error when compiling dotc/transform. Need to investigate. } - case OrType(tp11, tp12) => - isSubType(tp11, tp2) && isSubType(tp12, tp2) case tp1: PolyParam => + def flagNothingBound = { + if (!frozenConstraint && tp2.isRef(defn.NothingClass) && state.isGlobalCommittable) { + def msg = s"!!! instantiated to Nothing: $tp1, constraint = ${constraint.show}" + if (Config.flagInstantiationToNothing) assert(false, msg) + else ctx.log(msg) + } + true + } def comparePolyParam = - tp1 == tp2 || { - if (solvedConstraint && (constraint contains tp1)) isSubType(bounds(tp1).lo, tp2) - else - isSubTypeWhenFrozen(bounds(tp1).hi, tp2) || { - if (isConstrained(tp1)) - addConstraint(tp1, tp2, fromBelow = false) && { - if ((!frozenConstraint) && - (tp2 isRef defn.NothingClass) && - state.isGlobalCommittable) { - def msg = s"!!! instantiated to Nothing: $tp1, constraint = ${constraint.show}" - if (Config.flagInstantiationToNothing) assert(false, msg) - else ctx.log(msg) - } - true - } - else (ctx.mode is Mode.TypevarsMissContext) || thirdTry(tp1, tp2) - } + ctx.mode.is(Mode.TypevarsMissContext) || + isSubTypeWhenFrozen(bounds(tp1).hi, tp2) || { + if (canConstrain(tp1)) addConstraint(tp1, tp2, fromBelow = false) && flagNothingBound + else thirdTry(tp1, tp2) } comparePolyParam - case tp1: RefinedThis => + case tp1: ThisType => + tp2 match { + case tp2: TermRef if tp1.cls eq tp2.symbol.moduleClass => + isSubType(tp1.cls.owner.thisType, tp2.prefix) + case _ => + thirdTry(tp1, tp2) + } + case tp1: SkolemType => tp2 match { - case tp2: RefinedThis if tp1.binder.parent =:= tp2.binder.parent => true + case tp2: SkolemType if tp1 == tp2 => true case _ => thirdTry(tp1, tp2) } - case tp1: BoundType => - tp1 == tp2 || thirdTry(tp1, tp2) case tp1: TypeVar => - (tp1 eq tp2) || isSubType(tp1.underlying, tp2) + isSubType(tp1.underlying, tp2) case tp1: WildcardType => def compareWild = tp1.optBounds match { case TypeBounds(lo, _) => isSubType(lo, tp2) @@ -588,127 +283,65 @@ class TypeComparer(initctx: Context) extends DotClass { isSubType(tp1.ref, tp2) case tp1: AnnotatedType => isSubType(tp1.tpe, tp2) + case OrType(tp11, tp12) => + isSubType(tp11, tp2) && isSubType(tp12, tp2) case ErrorType => true case _ => thirdTry(tp1, tp2) } - def secondTryNamed(tp1: NamedType, tp2: Type): Boolean = { - def tryRebase2nd = { - val tp1rebased = rebase(tp1) - if (tp1rebased ne tp1) isSubType(tp1rebased, tp2) - else thirdTry(tp1, tp2) - } - tp1.info match { - // There was the following code, which was meant to implement this logic: - // If x has type A | B, then x.type <: C if - // x.type <: C assuming x has type A, and - // x.type <: C assuming x has type B. - // But it did not work, because derivedRef would always give back the same - // type and cache the denotation. So it ended up copmparing just one branch. - // The code seems to be unncessary for the tests and does not seems to help performance. - // So it is commented out. If we ever need to come back to this, we would have - // to create unchached TermRefs in order to avoid cross talk between the branches. - /* - case OrType(tp11, tp12) => - val sd = tp1.denot.asSingleDenotation - def derivedRef(tp: Type) = - NamedType(tp1.prefix, tp1.name, sd.derivedSingleDenotation(sd.symbol, tp)) - secondTry(OrType.make(derivedRef(tp11), derivedRef(tp12)), tp2) - */ - case TypeBounds(lo1, hi1) => - val gbounds1 = ctx.gadt.bounds(tp1.symbol) - if (gbounds1 != null) - isSubTypeWhenFrozen(gbounds1.hi, tp2) || - (ctx.mode is Mode.GADTflexible) && - narrowGADTBounds(tp1, TypeBounds(gbounds1.lo, gbounds1.hi & tp2)) || - tryRebase2nd - else if (lo1 eq hi1) isSubType(hi1, tp2) - else tryRebase2nd - case _ => - tryRebase2nd - } - } - - def thirdTry(tp1: Type, tp2: Type): Boolean = tp2 match { - case tp2: NamedType => - def tryRebase3rd = { - val tp2rebased = rebase(tp2) - if (tp2rebased ne tp2) isSubType(tp1, tp2rebased) - else fourthTry(tp1, tp2) + private def thirdTryNamed(tp1: Type, tp2: NamedType): Boolean = tp2.info match { + case TypeBounds(lo2, _) => + def compareGADT: Boolean = { + val gbounds2 = ctx.gadt.bounds(tp2.symbol) + (gbounds2 != null) && + (isSubTypeWhenFrozen(tp1, gbounds2.lo) || + narrowGADTBounds(tp2, tp1, isLowerBound = true)) } - def compareNamed: Boolean = tp2.info match { - case TypeBounds(lo2, hi2) => - val gbounds2 = ctx.gadt.bounds(tp2.symbol) - if (gbounds2 != null) - isSubTypeWhenFrozen(tp1, gbounds2.lo) || - (ctx.mode is Mode.GADTflexible) && - narrowGADTBounds(tp2, TypeBounds(gbounds2.lo | tp1, gbounds2.hi)) || - tryRebase3rd - else - ((frozenConstraint || !isCappable(tp1)) && isSubType(tp1, lo2) - || tryRebase3rd) + ((frozenConstraint || !isCappable(tp1)) && isSubType(tp1, lo2) || + compareGADT || + fourthTry(tp1, tp2)) - case _ => - val cls2 = tp2.symbol - if (cls2.isClass) { - val base = tp1.baseTypeRef(cls2) - if (base.exists && (base ne tp1)) return isSubType(base, tp2) - if (cls2 == defn.SingletonClass && tp1.isStable) return true - } - tryRebase3rd + case _ => + val cls2 = tp2.symbol + if (cls2.isClass) { + val base = tp1.baseTypeRef(cls2) + if (base.exists && (base ne tp1)) return isSubType(base, tp2) + if (cls2 == defn.SingletonClass && tp1.isStable) return true } - compareNamed - case tp2 @ RefinedType(parent2, name2) => - def qualifies(m: SingleDenotation) = isSubType(m.info, tp2.refinedInfo) - def memberMatches(mbr: Denotation): Boolean = mbr match { // inlined hasAltWith for performance - case mbr: SingleDenotation => qualifies(mbr) - case _ => mbr hasAltWith qualifies - } - def compareRefinedSlow: Boolean = { - def hasMatchingMember(name: Name): Boolean = /*>|>*/ ctx.traceIndented(s"hasMatchingMember($name) ${tp1.member(name).info.show}", subtyping) /*<|<*/ { - val tp1r = rebaseQual(tp1, name) - (memberMatches(narrowRefined(tp1r) member name) - || - { // special case for situations like: - // foo <: C { type T = foo.T } - tp2.refinedInfo match { - case rinfo: TypeAlias => - !ctx.phase.erasedTypes && (tp1r select name) =:= rinfo.alias - case _ => false - } - }) + fourthTry(tp1, tp2) + } + + private def thirdTry(tp1: Type, tp2: Type): Boolean = tp2 match { + case tp2: NamedType => + thirdTryNamed(tp1, tp2) + case tp2: PolyParam => + def comparePolyParam = + (ctx.mode is Mode.TypevarsMissContext) || + isSubTypeWhenFrozen(tp1, bounds(tp2).lo) || { + if (canConstrain(tp2)) addConstraint(tp2, tp1.widenExpr, fromBelow = true) + else fourthTry(tp1, tp2) } - val matchesParent = { - val saved = pendingRefinedBases - try { - addPendingName(name2, tp2, tp2) - isSubType(tp1, parent2) - } finally pendingRefinedBases = saved + comparePolyParam + case tp2: RefinedType => + def compareRefined: Boolean = { + val tp1w = tp1.widen + val skipped2 = skipMatching(tp1w, tp2) + if ((skipped2 eq tp2) || !Config.fastPathForRefinedSubtype) { + val name2 = tp2.refinedName + val normalPath = + isSubType(tp1, tp2.parent) && + ( name2 == nme.WILDCARD + || hasMatchingMember(name2, tp1, tp2) + || fourthTry(tp1, tp2) + ) + normalPath || + needsEtaLift(tp1, tp2) && tp1.testLifted(tp2.typeParams, isSubType(_, tp2)) } - (matchesParent && ( - name2 == nme.WILDCARD - || hasMatchingMember(name2) - || fourthTry(tp1, tp2)) - || needsEtaLift(tp1, tp2) && tp1.testLifted(tp2.typeParams, isSubType(_, tp2))) - } - def compareRefined: Boolean = tp1.widen match { - case tp1 @ RefinedType(parent1, name1) if name1 == name2 && name1.isTypeName => - normalizedInfo(tp1) match { - case bounds1 @ TypeBounds(lo1, hi1) if lo1 eq hi1 => - isSubType(bounds1, tp2.refinedInfo) && { - val saved = pendingRefinedBases - try { - addPendingName(name1, tp1, tp2) - isSubType(parent1, parent2) - } finally pendingRefinedBases = saved - } - case _ => - compareRefinedSlow - } - case _ => - compareRefinedSlow + else // fast path, in particular for refinements resulting from parameterization. + isSubType(tp1, skipped2) && + isSubRefinements(tp1w.asInstanceOf[RefinedType], tp2, skipped2) } compareRefined case OrType(tp21, tp22) => @@ -718,7 +351,7 @@ class TypeComparer(initctx: Context) extends DotClass { case tp1 @ MethodType(_, formals1) => (tp1.signature sameParams tp2.signature) && (if (Config.newMatch) subsumeParams(formals1, formals2, tp1.isJava, tp2.isJava) - else matchingParams(formals1, formals2, tp1.isJava, tp2.isJava)) && + else matchingParams(formals1, formals2, tp1.isJava, tp2.isJava)) && tp1.isImplicit == tp2.isImplicit && // needed? isSubType(tp1.resultType, tp2.resultType.subst(tp2, tp1)) case _ => @@ -729,8 +362,8 @@ class TypeComparer(initctx: Context) extends DotClass { def comparePoly = tp1 match { case tp1: PolyType => (tp1.signature sameParams tp2.signature) && - matchingTypeParams(tp1, tp2) && - isSubType(tp1.resultType, tp2.resultType.subst(tp2, tp1)) + matchingTypeParams(tp1, tp2) && + isSubType(tp1.resultType, tp2.resultType.subst(tp2, tp1)) case _ => false } @@ -750,8 +383,8 @@ class TypeComparer(initctx: Context) extends DotClass { case tp2 @ TypeBounds(lo2, hi2) => def compareTypeBounds = tp1 match { case tp1 @ TypeBounds(lo1, hi1) => - (tp2.variance > 0 && tp1.variance >= 0 || isSubType(lo2, lo1)) && - (tp2.variance < 0 && tp1.variance <= 0 || isSubType(hi1, hi2)) + (tp2.variance > 0 && tp1.variance >= 0 || (lo2 eq NothingType) || isSubType(lo2, lo1)) && + (tp2.variance < 0 && tp1.variance <= 0 || (hi2 eq AnyType) || isSubType(hi1, hi2)) case tp1: ClassInfo => val tt = tp1.typeRef isSubType(lo2, tt) && isSubType(tt, hi2) @@ -762,68 +395,81 @@ class TypeComparer(initctx: Context) extends DotClass { case ClassInfo(pre2, cls2, _, _, _) => def compareClassInfo = tp1 match { case ClassInfo(pre1, cls1, _, _, _) => - (cls1 eq cls2) && isSubType(pre2, pre1) + (cls1 eq cls2) && isSubType(pre1, pre2) case _ => false } compareClassInfo - case JavaArrayType(elem2) => - def compareJavaArray = tp1 match { - case JavaArrayType(elem1) => isSubType(elem1, elem2) - case _ => fourthTry(tp1, tp2) - } - compareJavaArray case _ => fourthTry(tp1, tp2) } - def fourthTry(tp1: Type, tp2: Type): Boolean = tp1 match { + private def fourthTry(tp1: Type, tp2: Type): Boolean = tp1 match { case tp1: TypeRef => tp1.info match { - case TypeBounds(lo1, hi1) => - isSubType(hi1, tp2) + case TypeBounds(_, hi1) => + def compareGADT = { + val gbounds1 = ctx.gadt.bounds(tp1.symbol) + (gbounds1 != null) && + (isSubTypeWhenFrozen(gbounds1.hi, tp2) || + narrowGADTBounds(tp1, tp2, isLowerBound = false)) + } + isSubType(hi1, tp2) || compareGADT case _ => def isNullable(tp: Type): Boolean = tp.dealias match { case tp: TypeRef => tp.symbol.isNullableClass case RefinedType(parent, _) => isNullable(parent) + case AndType(tp1, tp2) => isNullable(tp1) && isNullable(tp2) + case OrType(tp1, tp2) => isNullable(tp1) || isNullable(tp2) case _ => false } (tp1.symbol eq NothingClass) && tp2.isInstanceOf[ValueType] || (tp1.symbol eq NullClass) && isNullable(tp2) } case tp1: SingletonType => - isNewSubType(tp1.underlying.widenExpr, tp2) || { - // if tp2 == p.type and p: q.type then try tp1 <:< q.type as a last effort. - tp2 match { - case tp2: TermRef => - tp2.info match { - case tp2i: TermRef => - isSubType(tp1, tp2i) - case ExprType(tp2i: TermRef) if (ctx.phase.id > ctx.gettersPhase.id) => - isSubType(tp1, tp2i) - case _ => - false - } - case _ => - false - } + /** if `tp2 == p.type` and `p: q.type` then try `tp1 <:< q.type` as a last effort.*/ + def comparePaths = tp2 match { + case tp2: TermRef => + tp2.info match { + case tp2i: TermRef => + isSubType(tp1, tp2i) + case ExprType(tp2i: TermRef) if (ctx.phase.id > ctx.gettersPhase.id) => + // After getters, val x: T becomes def x: T + isSubType(tp1, tp2i) + case _ => + false + } + case _ => + false } + isNewSubType(tp1.underlying.widenExpr, tp2) || comparePaths case tp1: RefinedType => - { val saved = pendingRefinedBases - try { - addPendingName(tp1.refinedName, tp1, tp1) - isNewSubType(tp1.parent, tp2) - } - finally pendingRefinedBases = saved - } || needsEtaLift(tp2, tp1) && tp2.testLifted(tp1.typeParams, isSubType(tp1, _)) + isNewSubType(tp1.parent, tp2) || + needsEtaLift(tp2, tp1) && tp2.testLifted(tp1.typeParams, isSubType(tp1, _)) case AndType(tp11, tp12) => eitherIsSubType(tp11, tp2, tp12, tp2) case JavaArrayType(elem1) => - tp2 isRef ObjectClass + def compareJavaArray = tp2 match { + case JavaArrayType(elem2) => isSubType(elem1, elem2) + case _ => tp2 isRef ObjectClass + } + compareJavaArray case _ => false } + /** If `projection` is of the form T # Apply where `T` is an instance of a Lambda class, + * and `other` is not a type lambda projection, then convert `other` to a type lambda `U`, and + * continue with `T <:< U` if `inOrder` is true and `U <:< T` otherwise. + */ + def compareHK(projection: NamedType, other: Type, inOrder: Boolean) = + projection.name == tpnme.Apply && { + val lambda = projection.prefix.LambdaClass(forcing = true) + lambda.exists && !other.isLambda && + other.testLifted(lambda.typeParams, + if (inOrder) isSubType(projection.prefix, _) else isSubType(_, projection.prefix)) + } + /** Returns true iff either `tp11 <:< tp21` or `tp12 <:< tp22`, trying at the same time * to keep the constraint as wide as possible. Specifically, if * @@ -852,7 +498,7 @@ class TypeComparer(initctx: Context) extends DotClass { * Here, each precondition leads to a different constraint, and neither of * the two post-constraints subsumes the other. */ - def eitherIsSubType(tp11: Type, tp21: Type, tp12: Type, tp22: Type) = { + private def eitherIsSubType(tp11: Type, tp21: Type, tp12: Type, tp22: Type) = { val preConstraint = constraint isSubType(tp11, tp21) && { val leftConstraint = constraint @@ -870,8 +516,74 @@ class TypeComparer(initctx: Context) extends DotClass { if (isCovered(tp1) && isCovered(tp2)) { //println(s"useless subtype: $tp1 <:< $tp2") false + } else isSubType(tp1, tp2) + + /** Does type `tp1` have a member with name `name` whose normalized type is a subtype of + * the normalized type of the refinement `tp2`? + * Normalization is as follows: If `tp2` contains a skolem to its refinement type, + * rebase both itself and the member info of `tp` on a freshly created skolem type. + */ + protected def hasMatchingMember(name: Name, tp1: Type, tp2: RefinedType): Boolean = { + val saved = skolemsOutstanding + try { + val rebindNeeded = tp2.refinementRefersToThis + val base = if (rebindNeeded) ensureSingleton(tp1) else tp1 + val rinfo2 = if (rebindNeeded) tp2.refinedInfo.substSkolem(tp2, base) else tp2.refinedInfo + def qualifies(m: SingleDenotation) = isSubType(m.info, rinfo2) + def memberMatches(mbr: Denotation): Boolean = mbr match { // inlined hasAltWith for performance + case mbr: SingleDenotation => qualifies(mbr) + case _ => mbr hasAltWith qualifies + } + /*>|>*/ ctx.traceIndented(i"hasMatchingMember($base . $name :? ${tp2.refinedInfo}) ${base.member(name).info.show} $rinfo2", subtyping) /*<|<*/ { + memberMatches(base member name) || + tp1.isInstanceOf[SingletonType] && + { // special case for situations like: + // class C { type T } + // val foo: C + // foo.type <: C { type T = foo.T } + rinfo2 match { + case rinfo2: TypeAlias => (base select name) =:= rinfo2.alias + case _ => false + } + } + } } - else isSubType(tp1, tp2) + finally skolemsOutstanding = saved + } + + /** Skip refinements in `tp2` which match corresponding refinements in `tp1`. + * "Match" means: + * - they appear in the same order, + * - they refine the same names, + * - the refinement in `tp1` is an alias type, and + * - neither refinement refers back to the refined type via a refined this. + * @return The parent type of `tp2` after skipping the matching refinements. + */ + private def skipMatching(tp1: Type, tp2: RefinedType): Type = tp1 match { + case tp1 @ RefinedType(parent1, name1) + if name1 == tp2.refinedName && + tp1.refinedInfo.isInstanceOf[TypeAlias] && + !tp2.refinementRefersToThis && + !tp1.refinementRefersToThis => + tp2.parent match { + case parent2: RefinedType => skipMatching(parent1, parent2) + case parent2 => parent2 + } + case _ => tp2 + } + + /** Are refinements in `tp1` pairwise subtypes of the refinements of `tp2` + * up to parent type `limit`? + * @pre `tp1` has the necessary number of refinements, they are type aliases, + * and their names match the corresponding refinements in `tp2`. + * Further, no refinement refers back to the refined type via a refined this. + * The precondition is established by `skipMatching`. + */ + private def isSubRefinements(tp1: RefinedType, tp2: RefinedType, limit: Type): Boolean = + isSubType(tp1.refinedInfo, tp2.refinedInfo) && ( + (tp2.parent eq limit) || + isSubRefinements( + tp1.parent.asInstanceOf[RefinedType], tp2.parent.asInstanceOf[RefinedType], limit)) /** A type has been covered previously in subtype checking if it * is some combination of TypeRefs that point to classes, where the @@ -886,15 +598,9 @@ class TypeComparer(initctx: Context) extends DotClass { case _ => false } - /** The current bounds of type parameter `param` */ - def bounds(param: PolyParam): TypeBounds = constraint at param match { - case bounds: TypeBounds if !ignoreConstraint => bounds - case _ => param.binder.paramBounds(param.paramNum) - } - /** Defer constraining type variables when compared against prototypes */ def isMatchedByProto(proto: ProtoType, tp: Type) = tp.stripTypeVar match { - case tp: PolyParam if !solvedConstraint && (constraint contains tp) => true + case tp: PolyParam if constraint contains tp => true case _ => proto.isMatchedBy(tp) } @@ -904,23 +610,36 @@ class TypeComparer(initctx: Context) extends DotClass { * we should first unroll `tp1` until we hit the type variable and bind the * type variable with (the corresponding type in) `tp2` instead. */ - def isCappable(tp: Type): Boolean = tp match { - case tp: PolyParam => !solvedConstraint && (constraint contains tp) + private def isCappable(tp: Type): Boolean = tp match { + case tp: PolyParam => constraint contains tp case tp: TypeProxy => isCappable(tp.underlying) case tp: AndOrType => isCappable(tp.tp1) || isCappable(tp.tp2) case _ => false } /** Does `tp` need to be eta lifted to be comparable to `target`? */ - def needsEtaLift(tp: Type, target: RefinedType) = { + private def needsEtaLift(tp: Type, target: RefinedType): Boolean = { + //default.echo(i"needs eta $tp $target?", { val name = target.refinedName (name.isLambdaArgName || (name eq tpnme.Apply)) && target.isLambda && tp.exists && !tp.isLambda + //}) } - def narrowGADTBounds(tr: NamedType, bounds: TypeBounds): Boolean = - isSubType(bounds.lo, bounds.hi) && - { ctx.gadt.setBounds(tr.symbol, bounds); true } + private def narrowGADTBounds(tr: NamedType, bound: Type, isLowerBound: Boolean): Boolean = + ctx.mode.is(Mode.GADTflexible) && { + val tparam = tr.symbol + val bound1 = deSkolemize(bound, toSuper = isLowerBound) + typr.println(s"narrow gadt bound of $tparam: ${tparam.info} from ${if (isLowerBound) "below" else "above"} to $bound1 ${bound1.isRef(tparam)}") + !bound1.isRef(tparam) && { + val oldBounds = ctx.gadt.bounds(tparam) + val newBounds = + if (isLowerBound) TypeBounds(oldBounds.lo | bound1, oldBounds.hi) + else TypeBounds(oldBounds.lo, oldBounds.hi & bound1) + isSubType(newBounds.lo, newBounds.hi) && + { ctx.gadt.setBounds(tparam, newBounds); true } + } + } // Tests around `matches` @@ -1214,19 +933,20 @@ class TypeComparer(initctx: Context) extends DotClass { case tp1: TypeBounds => tp2 match { case tp2: TypeBounds => tp1 & tp2 + case tp2: ClassInfo if tp1 contains tp2.typeRef => tp2 case _ => andConflict(tp1, tp2) } case tp1: ClassInfo => tp2 match { - case tp2: ClassInfo if tp1.cls eq tp2.cls => - tp1.derivedClassInfo(tp1.prefix & tp2.prefix) - case _ => - andConflict(tp1, tp2) + case tp2: ClassInfo if tp1.cls eq tp2.cls => tp1.derivedClassInfo(tp1.prefix & tp2.prefix) + case tp2: TypeBounds if tp2 contains tp1.typeRef => tp1 + case _ => andConflict(tp1, tp2) } case tp1 @ MethodType(names1, formals1) => tp2 match { case tp2 @ MethodType(names2, formals2) - if Config.newMatch && (tp1.isImplicit == tp2.isImplicit) && formals1.hasSameLengthAs(formals2) => + if Config.newMatch && tp1.signature.sameParams(tp2.signature) && + tp1.isImplicit == tp2.isImplicit => tp1.derivedMethodType( mergeNames(names1, names2, nme.syntheticParamName), (formals1 zipWithConserve formals2)(_ | _), @@ -1279,19 +999,20 @@ class TypeComparer(initctx: Context) extends DotClass { case tp1: TypeBounds => tp2 match { case tp2: TypeBounds => tp1 | tp2 + case tp2: ClassInfo if tp1 contains tp2.typeRef => tp1 case _ => orConflict(tp1, tp2) } case tp1: ClassInfo => tp2 match { - case tp2: ClassInfo if tp1.cls eq tp2.cls => - tp1.derivedClassInfo(tp1.prefix | tp2.prefix) - case _ => - orConflict(tp1, tp2) + case tp2: ClassInfo if tp1.cls eq tp2.cls => tp1.derivedClassInfo(tp1.prefix | tp2.prefix) + case tp2: TypeBounds if tp2 contains tp1.typeRef => tp2 + case _ => orConflict(tp1, tp2) } case tp1 @ MethodType(names1, formals1) => tp2 match { case tp2 @ MethodType(names2, formals2) - if Config.newMatch && (tp1.isImplicit == tp2.isImplicit) && formals1.hasSameLengthAs(formals2) => + if Config.newMatch && tp1.signature.sameParams(tp2.signature) && + tp1.isImplicit == tp2.isImplicit => tp1.derivedMethodType( mergeNames(names1, names2, nme.syntheticParamName), (formals1 zipWithConserve formals2)(_ & _), @@ -1400,31 +1121,52 @@ class TypeComparer(initctx: Context) extends DotClass { false } - /** Constraint `c1` subsumes constraint `c2`, if under `c2` as constraint we have - * for all poly params `p` defined in `c2` as `p >: L2 <: U2`: - * - * c1 defines p with bounds p >: L1 <: U1, and - * L2 <: L1, and - * U1 <: U2 - * - * Both `c1` and `c2` are required to derive from constraint `pre`, possibly - * narrowing it with further bounds. - */ - def subsumes(c1: Constraint, c2: Constraint, pre: Constraint): Boolean = - if (c2 eq pre) true - else if (c1 eq pre) false - else { - val saved = constraint - try - c2.forallParams(p => c1.contains(p) && isSubType(c1.bounds(p), c2.bounds(p))) - finally constraint = saved - } - /** A new type comparer of the same type as this one, using the given context. */ def copyIn(ctx: Context) = new TypeComparer(ctx) + // ----------- Diagnostics -------------------------------------------------- + /** A hook for showing subtype traces. Overridden in ExplainingTypeComparer */ def traceIndented[T](str: String)(op: => T): T = op + + private def traceInfo(tp1: Type, tp2: Type) = + s"${tp1.show} <:< ${tp2.show}" + { + if (ctx.settings.verbose.value || Config.verboseExplainSubtype) { + s" ${tp1.getClass}, ${tp2.getClass}" + + (if (frozenConstraint) " frozen" else "") + + (if (ctx.mode is Mode.TypevarsMissContext) " tvars-miss-ctx" else "") + } + else "" + } + + /** Show subtype goal that led to an assertion failure */ + def showGoal(tp1: Type, tp2: Type) = { + println(disambiguated(implicit ctx => s"assertion failure for ${tp1.show} <:< ${tp2.show}, frozen = $frozenConstraint")) + def explainPoly(tp: Type) = tp match { + case tp: PolyParam => println(s"polyparam ${tp.show} found in ${tp.binder.show}") + case tp: TypeRef if tp.symbol.exists => println(s"typeref ${tp.show} found in ${tp.symbol.owner.show}") + case tp: TypeVar => println(s"typevar ${tp.show}, origin = ${tp.origin}") + case _ => println(s"${tp.show} is a ${tp.getClass}") + } + explainPoly(tp1) + explainPoly(tp2) + } + + /** Record statistics about the total number of subtype checks + * and the number of "successful" subtype checks, i.e. checks + * that form part of a subtype derivation tree that's ultimately successful. + */ + def recordStatistics(result: Boolean, prevSuccessCount: Int) = { + // Stats.record(s"isSubType ${tp1.show} <:< ${tp2.show}") + totalCount += 1 + if (result) successCount += 1 else successCount = prevSuccessCount + if (recCount == 0) { + Stats.record("successful subType", successCount) + Stats.record("total subType", totalCount) + successCount = 0 + totalCount = 0 + } + } } object TypeComparer { @@ -1465,6 +1207,11 @@ class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) { super.isSubType(tp1, tp2) } + override def hasMatchingMember(name: Name, tp1: Type, tp2: RefinedType): Boolean = + traceIndented(s"hasMatchingMember(${show(tp1)} . $name, ${show(tp2.refinedInfo)}), member = ${show(tp1.member(name).info)}") { + super.hasMatchingMember(name, tp1, tp2) + } + override def lub(tp1: Type, tp2: Type) = traceIndented(s"lub(${show(tp1)}, ${show(tp2)})") { super.lub(tp1, tp2) diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index 260e2f6d6b9a..3e04eb037537 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -86,7 +86,7 @@ trait TypeOps { this: Context => class SimplifyMap extends TypeMap { def apply(tp: Type) = simplify(tp, this) } - + /** Approximate union type by intersection of its dominators. * See Type#approximateUnion for an explanation. */ @@ -330,7 +330,7 @@ trait TypeOps { this: Context => } parentRefs } - + /** An argument bounds violation is a triple consisting of * - the argument tree * - a string "upper" or "lower" indicating which bound is violated diff --git a/src/dotty/tools/dotc/core/TyperState.scala b/src/dotty/tools/dotc/core/TyperState.scala index de5e0a96168c..1079af510d66 100644 --- a/src/dotty/tools/dotc/core/TyperState.scala +++ b/src/dotty/tools/dotc/core/TyperState.scala @@ -17,7 +17,8 @@ class TyperState(r: Reporter) extends DotClass with Showable { def reporter = r /** The current constraint set */ - def constraint: Constraint = new Constraint(SimpleMap.Empty, SimpleMap.Empty) + def constraint: Constraint = + new OrderingConstraint(SimpleMap.Empty, SimpleMap.Empty, SimpleMap.Empty) def constraint_=(c: Constraint): Unit = {} /** The uninstantiated variables */ @@ -38,7 +39,7 @@ class TyperState(r: Reporter) extends DotClass with Showable { * 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): Type = constraint.at(tvar.origin) match { + def instType(tvar: TypeVar)(implicit ctx: Context): Type = constraint.entry(tvar.origin) match { case _: TypeBounds => NoType case tp: PolyParam => var tvar1 = constraint.typeVarOfParam(tp) @@ -164,17 +165,16 @@ extends TyperState(r) { * found a better solution. */ override def tryWithFallback[T](op: => T)(fallback: => T)(implicit ctx: Context): T = { + val storeReporter = new StoreReporter val savedReporter = myReporter + myReporter = storeReporter val savedConstraint = myConstraint - myReporter = new StoreReporter - val result = op - try - if (!reporter.hasErrors) result - else { - myConstraint = savedConstraint - fallback - } - finally myReporter = savedReporter + val result = try op finally myReporter = savedReporter + if (!storeReporter.hasErrors) result + else { + myConstraint = savedConstraint + fallback + } } override def toText(printer: Printer): Text = constraint.toText(printer) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index b8e0a48c129c..ef8aa78b63dd 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -49,7 +49,7 @@ object Types { * | | +--- SuperType * | | +--- ConstantType * | | +--- MethodParam - * | | +--- RefinedThis + * | | +--- SkolemType * | +- PolyParam * | +- RefinedType * | +- TypeBounds @@ -419,6 +419,8 @@ object Types { if mt.paramTypes.isEmpty && (tp.symbol is Stable) => mt.resultType case tp1 => tp1 }) + case tp: PolyParam => + goParam(tp) case tp: TypeProxy => go(tp.underlying) case tp: ClassInfo => @@ -436,7 +438,9 @@ object Types { } def goRefined(tp: RefinedType) = { val pdenot = go(tp.parent) - val rinfo = tp.refinedInfo.substThis(tp, pre) + val rinfo = + if (tp.refinementRefersToThis) tp.refinedInfo.substSkolem(tp, pre) + else tp.refinedInfo if (name.isTypeName) { // simplified case that runs more efficiently val jointInfo = if (rinfo.isAlias) rinfo else pdenot.info & rinfo pdenot.asSingleDenotation.derivedSingleDenotation(pdenot.symbol, jointInfo) @@ -458,6 +462,16 @@ object Types { // loadClassWithPrivateInnerAndSubSelf in ShowClassTests go(tp.cls.typeRef) orElse d } + def goParam(tp: PolyParam) = { + val next = tp.underlying + ctx.typerState.constraint.entry(tp) match { + case bounds: TypeBounds if bounds ne next => + ctx.typerState.ephemeral = true + go(bounds.hi) + case _ => + go(next) + } + } def goAnd(l: Type, r: Type) = go(l) & (go(r), pre) def goOr(l: Type, r: Type) = go(l) | (go(r), pre) go(this) @@ -742,7 +756,7 @@ object Types { } /** A prefix-less refined this or a termRef to a new skolem symbol - * that has the given type as info + * that has the given type as info. */ def narrow(implicit ctx: Context): TermRef = TermRef(NoPrefix, ctx.newSkolem(this)) @@ -756,35 +770,29 @@ object Types { * P { ... type T = / += / -= U ... } # T * * to just U. Does not perform the reduction if the resulting type would contain - * a reference to the "this" of the current refined type. + * a reference to the "this" of the current refined type. But does follow + * aliases in order to avoid such references. Example: + * + * Lambda$I { type $hk$Arg0 = String, type Apply = this<0>.$hk$Arg0 } # Apply + * + * Here, the refinement for `Apply` has a refined this node, yet dereferencing ones more + * yields `String` as the result of lookupRefined. */ def lookupRefined(name: Name)(implicit ctx: Context): Type = { - - def dependsOnRefinedThis(tp: Type): Boolean = tp.stripTypeVar match { - case tp @ TypeRef(RefinedThis(rt), _) if rt refines this => - tp.info match { - case TypeAlias(alias) => dependsOnRefinedThis(alias) - case _ => true - } - case RefinedThis(rt) => rt refines this - case tp: NamedType => - !tp.symbol.isStatic && dependsOnRefinedThis(tp.prefix) - case tp: RefinedType => dependsOnRefinedThis(tp.refinedInfo) || dependsOnRefinedThis(tp.parent) - case tp: TypeBounds => dependsOnRefinedThis(tp.lo) || dependsOnRefinedThis(tp.hi) - case tp: AnnotatedType => dependsOnRefinedThis(tp.underlying) - case tp: AndOrType => dependsOnRefinedThis(tp.tp1) || dependsOnRefinedThis(tp.tp2) - case _ => false - } - def loop(pre: Type): Type = pre.stripTypeVar match { case pre: RefinedType => if (pre.refinedName ne name) loop(pre.parent) - else this.member(name).info match { - case TypeAlias(tp) if !dependsOnRefinedThis(tp) => tp - case _ => NoType + else pre.refinedInfo match { + case TypeAlias(tp) => + if (!pre.refinementRefersToThis) tp + else tp match { + case TypeRef(SkolemType(`pre`), alias) => lookupRefined(alias) + case _ => NoType + } + case _ => loop(pre.parent) } - case RefinedThis(rt) => - rt.lookupRefined(name) + case SkolemType(binder) => + binder.lookupRefined(name) case pre: WildcardType => WildcardType case _ => @@ -944,9 +952,9 @@ object Types { final def substThisUnlessStatic(cls: ClassSymbol, tp: Type)(implicit ctx: Context): Type = if (cls.isStaticOwner) this else ctx.substThis(this, cls, tp, null) - /** Substitute all occurrences of `RefinedThis(rt)` by `tp` */ - final def substThis(rt: RefinedType, tp: Type)(implicit ctx: Context): Type = - ctx.substThis(this, rt, tp, null) + /** Substitute all occurrences of `SkolemType(binder)` by `tp` */ + final def substSkolem(binder: Type, tp: Type)(implicit ctx: Context): Type = + ctx.substSkolem(this, binder, tp, null) /** Substitute a bound type by some other type */ final def substParam(from: ParamType, to: Type)(implicit ctx: Context): Type = @@ -1338,7 +1346,7 @@ object Types { * to an (unbounded) wildcard type. * * (2) Reduce a type-ref `T { X = U; ... } # X` to `U` - * provided `U` does not refer with a RefinedThis to the + * provided `U` does not refer with a SkolemType to the * refinement type `T { X = U; ... }` */ def reduceProjection(implicit ctx: Context): Type = { @@ -1741,6 +1749,17 @@ object Types { extends CachedProxyType with BindingType with ValueType { val refinedInfo: Type + + private var refinementRefersToThisCache: Boolean = _ + private var refinementRefersToThisKnown: Boolean = false + + def refinementRefersToThis(implicit ctx: Context): Boolean = { + if (!refinementRefersToThisKnown) { + refinementRefersToThisCache = refinedInfo.containsSkolemType(this) + refinementRefersToThisKnown = true + } + refinementRefersToThisCache + } override def underlying(implicit ctx: Context) = parent @@ -1771,7 +1790,8 @@ object Types { && !parent.isLambda) derivedRefinedType(parent.EtaExpand, refinedName, refinedInfo) else - RefinedType(parent, refinedName, rt => refinedInfo.substThis(this, RefinedThis(rt))) + if (false) RefinedType(parent, refinedName, refinedInfo) + else RefinedType(parent, refinedName, rt => refinedInfo.substSkolem(this, SkolemType(rt))) } override def equals(that: Any) = that match { @@ -1783,7 +1803,7 @@ object Types { false } override def computeHash = doHash(refinedName, refinedInfo, parent) - override def toString = s"RefinedType($parent, $refinedName, $refinedInfo)" + override def toString = s"RefinedType($parent, $refinedName, $refinedInfo | $hashCode)" // !!! TODO: remove } class CachedRefinedType(parent: Type, refinedName: Name, infoFn: RefinedType => Type) extends RefinedType(parent, refinedName) { @@ -2101,10 +2121,10 @@ object Types { } } - // ----- Bound types: MethodParam, PolyParam, RefinedThis -------------------------- + // ----- Bound types: MethodParam, PolyParam, SkolemType -------------------------- abstract class BoundType extends CachedProxyType with ValueType { - type BT <: BindingType + type BT <: Type def binder: BT // Dotty deviation: copyBoundType was copy, but // dotty generates copy methods always automatically, and therefore @@ -2143,6 +2163,7 @@ object Types { } } + /** TODO Some docs would be nice here! */ case class PolyParam(binder: PolyType, paramNum: Int) extends ParamType { type BT = PolyType def copyBoundType(bt: BT) = PolyParam(bt, paramNum) @@ -2165,21 +2186,28 @@ object Types { override def toString = s"PolyParam(${binder.paramNames(paramNum)})" override def computeHash = doHash(paramNum, binder) + override def equals(that: Any) = that match { + case that: PolyParam => + (this.binder eq that.binder) && this.paramNum == that.paramNum + case _ => + false + } } - case class RefinedThis(binder: RefinedType) extends BoundType with SingletonType { - type BT = RefinedType + /** A skolem type reference with underlying type `binder`. */ + case class SkolemType(binder: Type) extends BoundType with SingletonType { + type BT = Type override def underlying(implicit ctx: Context) = binder - def copyBoundType(bt: BT) = RefinedThis(bt) - + def copyBoundType(bt: BT) = SkolemType(bt) + // need to customize hashCode and equals to prevent infinite recursion for // refinements that refer to the refinement type via this override def computeHash = addDelta(binder.identityHash, 41) override def equals(that: Any) = that match { - case that: RefinedThis => this.binder eq that.binder + case that: SkolemType => this.binder eq that.binder case _ => false } - override def toString = s"RefinedThis(${binder.hashCode})" + override def toString = s"SkolemType(${binder.hashCode})" } // ------------ Type variables ---------------------------------------- @@ -2235,7 +2263,7 @@ object Types { * is also a singleton type. */ def instantiate(fromBelow: Boolean)(implicit ctx: Context): Type = { - def upperBound = ctx.typerState.constraint.bounds(origin).hi + def upperBound = ctx.typerState.constraint.fullUpperBound(origin) def isSingleton(tp: Type): Boolean = tp match { case tp: SingletonType => true case AndType(tp1, tp2) => isSingleton(tp1) | isSingleton(tp2) @@ -2988,7 +3016,7 @@ object Types { // ----- Debug --------------------------------------------------------- var debugTrace = false - + val watchList = List[String]( ) map (_.toTypeName) diff --git a/src/dotty/tools/dotc/core/pickling/UnPickler.scala b/src/dotty/tools/dotc/core/pickling/UnPickler.scala index 72804870014d..894ad8f803fc 100644 --- a/src/dotty/tools/dotc/core/pickling/UnPickler.scala +++ b/src/dotty/tools/dotc/core/pickling/UnPickler.scala @@ -158,9 +158,6 @@ class UnPickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClassRoot: /** A map from symbols to their associated `decls` scopes */ private val symScopes = mutable.AnyRefMap[Symbol, Scope]() - /** A map from refinement classes to their associated refinement types */ - private val refinementTypes = mutable.AnyRefMap[Symbol, RefinedType]() - protected def errorBadSignature(msg: String, original: Option[RuntimeException] = None)(implicit ctx: Context) = { val ex = new BadSignature( sm"""error reading Scala signature of $classRoot from $source: @@ -612,9 +609,7 @@ class UnPickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClassRoot: case NOPREFIXtpe => NoPrefix case THIStpe => - val cls = readSymbolRef().asClass - if (isRefinementClass(cls)) RefinedThis(refinementTypes(cls)) - else cls.thisType + readSymbolRef().thisType case SINGLEtpe => val pre = readTypeRef() val sym = readDisambiguatedSymbolRef(_.info.isParameterless) @@ -665,12 +660,13 @@ class UnPickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClassRoot: val parent = parents.reduceLeft(AndType(_, _)) if (decls.isEmpty) parent else { - def addRefinement(tp: Type, sym: Symbol) = - RefinedType(tp, sym.name, sym.info) - val result = (parent /: decls.toList)(addRefinement).asInstanceOf[RefinedType] - assert(!refinementTypes.isDefinedAt(clazz), clazz + "/" + decls) - refinementTypes(clazz) = result - result + def addRefinement(tp: Type, sym: Symbol) = { + def subst(info: Type, rt: RefinedType) = + if (clazz.isClass) info.substThis(clazz.asClass, SkolemType(rt)) + else info // turns out some symbols read into `clazz` are not classes, not sure why this is the case. + RefinedType(tp, sym.name, subst(sym.info, _)) + } + (parent /: decls.toList)(addRefinement).asInstanceOf[RefinedType] } case CLASSINFOtpe => val clazz = readSymbolRef() diff --git a/src/dotty/tools/dotc/printing/PlainPrinter.scala b/src/dotty/tools/dotc/printing/PlainPrinter.scala index e58775b65821..1a6cc77b2991 100644 --- a/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -145,10 +145,10 @@ class PlainPrinter(_ctx: Context) extends Printer { if (tp.isInstantiated) toTextLocal(tp.instanceOpt) ~ "'" // debug for now, so that we can see where the TypeVars are. else { - val bounds = ctx.typerState.constraint.at(tp.origin) match { - case bounds: TypeBounds => bounds - case _ => TypeBounds.empty - } + val constr = ctx.typerState.constraint + val bounds = + if (constr.contains(tp)) constr.fullBounds(tp.origin) + else TypeBounds.empty "(" ~ toText(tp.origin) ~ "?" ~ toText(bounds) ~ ")" } case _ => @@ -207,8 +207,11 @@ class PlainPrinter(_ctx: Context) extends Printer { toText(value) case MethodParam(mt, idx) => nameString(mt.paramNames(idx)) - case RefinedThis(_) => - "this" + case sk: SkolemType => + sk.binder match { + case rt: RefinedType => s"${nameString(rt.typeSymbol)}{...}.this" + case _ => "" + } } } diff --git a/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala index b55f8e8e01f5..babf4250330e 100644 --- a/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -112,7 +112,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { val hideType = tp.symbol is TypeParam | TypeArgument | ExpandedName if (hideType && !ctx.phase.erasedTypes && !tp.symbol.isCompleting) { tp.info match { - case TypeAlias(hi) => return toText(hi) + case TypeAlias(alias) => return toText(alias) case _ => if (tp.prefix.isInstanceOf[ThisType]) return nameString(tp.symbol) } } diff --git a/src/dotty/tools/dotc/typer/ErrorReporting.scala b/src/dotty/tools/dotc/typer/ErrorReporting.scala index 8e8cf58f9b5c..2ed720f83444 100644 --- a/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -96,7 +96,7 @@ object ErrorReporting { def patternConstrStr(tree: Tree): String = ??? def typeMismatch(tree: Tree, pt: Type, implicitFailure: SearchFailure = NoImplicitMatches): Tree = { - errorTree(tree, typeMismatchStr(tree.tpe, pt) + implicitFailure.postscript) + errorTree(tree, typeMismatchStr(normalize(tree.tpe, pt), pt) + implicitFailure.postscript) } /** A subtype log explaining why `found` does not conform to `expected` */ diff --git a/src/dotty/tools/dotc/typer/Implicits.scala b/src/dotty/tools/dotc/typer/Implicits.scala index 82424164ac37..24d9ebf6d8b7 100644 --- a/src/dotty/tools/dotc/typer/Implicits.scala +++ b/src/dotty/tools/dotc/typer/Implicits.scala @@ -32,7 +32,7 @@ import collection.mutable object Implicits { /** A common base class of contextual implicits and of-type implicits which - * represents as set of implicit references. + * represents a set of implicit references. */ abstract class ImplicitRefs(initctx: Context) { implicit val ctx: Context = @@ -450,7 +450,10 @@ trait Implicits { self: Typer => private def nestedContext = ctx.fresh.setMode(ctx.mode &~ Mode.ImplicitsEnabled) private def implicitProto(resultType: Type, f: Type => Type) = - if (argument.isEmpty) f(resultType) else ViewProto(f(argument.tpe.widen), f(resultType)) + if (argument.isEmpty) f(resultType) else ViewProto(f(argument.tpe/*.widen*/), f(resultType)) + // !!! TODO: check performance implications + // If we do the widen, SyntheticMethods, line 66 fails to compile + // val synthetic = sym.copy(...) assert(argument.isEmpty || argument.tpe.isValueType || argument.tpe.isInstanceOf[ExprType], d"found: ${argument.tpe}, expected: $pt") diff --git a/src/dotty/tools/dotc/typer/Inferencing.scala b/src/dotty/tools/dotc/typer/Inferencing.scala index 05be46f29145..e41b2d194274 100644 --- a/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/src/dotty/tools/dotc/typer/Inferencing.scala @@ -203,7 +203,7 @@ trait Inferencing { this: Checking => if (v == 1) tvar.instantiate(fromBelow = false) else if (v == -1) tvar.instantiate(fromBelow = true) else { - val bounds = ctx.typerState.constraint.bounds(tvar.origin) + val bounds = ctx.typerState.constraint.fullBounds(tvar.origin) if (!(bounds.hi <:< bounds.lo)) result = Some(tvar) tvar.instantiate(fromBelow = false) } diff --git a/src/dotty/tools/dotc/typer/ProtoTypes.scala b/src/dotty/tools/dotc/typer/ProtoTypes.scala index 98300f0b019e..521f0deaae0e 100644 --- a/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -325,6 +325,7 @@ object ProtoTypes { else pt val tvars = if (owningTree.isEmpty) Nil else newTypeVars(added) state.constraint = state.constraint.add(added, tvars) + ctx.typeComparer.initialize(added) (added, tvars) } @@ -375,8 +376,8 @@ object ProtoTypes { tp.derivedRefinedType(wildApprox(tp.parent, theMap), tp.refinedName, wildApprox(tp.refinedInfo, theMap)) case tp: TypeAlias => // default case, inlined for speed tp.derivedTypeAlias(wildApprox(tp.alias, theMap)) - case tp @ PolyParam(poly, pnum) => - ctx.typerState.constraint.at(tp) match { + case tp @ PolyParam(poly, pnum) => // !!! todo adapt to TrackingConstraint + ctx.typerState.constraint.entry(tp) match { case bounds: TypeBounds => wildApprox(WildcardType(bounds)) case NoType => WildcardType(wildApprox(poly.paramBounds(pnum)).bounds) case inst => wildApprox(inst) diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 0ff47b36d024..9958b3b64ca2 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -791,7 +791,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit checkRefinementNonCyclic(refinement, refineCls, seen) val rsym = refinement.symbol val rinfo = if (rsym is Accessor) rsym.info.resultType else rsym.info - RefinedType(parent, rsym.name, rt => rinfo.substThis(refineCls, RefinedThis(rt))) + RefinedType(parent, rsym.name, rt => rinfo.substThis(refineCls, SkolemType(rt))) // todo later: check that refinement is within bounds } val res = cpy.RefinedTypeTree(tree)(tpt1, refinements1) withType diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 3f1170e40096..89ac2b6c4e45 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -24,9 +24,6 @@ class tests extends CompilerTest { val failedUnderscore = List("-Ystop-before:collectEntryPoints") // #289 val failedOther = List("-Ystop-before:collectEntryPoints") // some non-obvious reason. need to look deeper - - - val twice = List("#runs", "2", "-YnoDoubleBindings") val allowDeepSubtypes = defaultOptions diff List("-Yno-deep-subtypes") @@ -120,6 +117,7 @@ class tests extends CompilerTest { @Test def neg_i0091_infpaths = compileFile(negDir, "i0091-infpaths", xerrors = 3) @Test def neg_i0248_inherit_refined = compileFile(negDir, "i0248-inherit-refined", xerrors = 4) @Test def neg_i0281 = compileFile(negDir, "i0281-null-primitive-conforms", xerrors = 3) + @Test def neg_moduleSubtyping = compileFile(negDir, "moduleSubtyping", xerrors = 4) @Test def dotc = compileDir(dotcDir + "tools/dotc", failedOther)(allowDeepSubtypes) @Test def dotc_ast = compileDir(dotcDir + "tools/dotc/ast", failedOther) // similar to dotc_config @@ -129,7 +127,7 @@ class tests extends CompilerTest { @Test def dotc_core_pickling = compileDir(dotcDir + "tools/dotc/core/pickling", failedOther)(allowDeepSubtypes) // Cannot emit primitive conversion from V to Z - @Test def dotc_transform = compileDir(dotcDir + "tools/dotc/transform", failedbyName)(allowDeepSubtypes) + @Test def dotc_transform = compileDir(dotcDir + "tools/dotc/transform", failedbyName) @Test def dotc_parsing = compileDir(dotcDir + "tools/dotc/parsing", failedOther) // Expected primitive types I - Ljava/lang/Object diff --git a/tests/neg/moduleSubtyping.scala b/tests/neg/moduleSubtyping.scala new file mode 100644 index 000000000000..18e93d5ac616 --- /dev/null +++ b/tests/neg/moduleSubtyping.scala @@ -0,0 +1,23 @@ +class C { + + object o { + + var a: C.this.o.type = ??? + var b: this.type = ??? + a = b // OK + b = a // OK + + var c: Test.o.type = ??? + a = c // error + b = c // error + c = a // error + c = b // error + } + +} + +object Test extends C { + + + +} diff --git a/tests/pending/pos/channels.scala b/tests/pending/pos/channels.scala index b2f0cdc32135..77736305f474 100644 --- a/tests/pending/pos/channels.scala +++ b/tests/pending/pos/channels.scala @@ -1,3 +1,5 @@ +// To compile this test, we need some more elaborate GADT capabilities. +// Not sure yet we should invest to get them. class Channel[a] import collection.mutable.Set @@ -16,7 +18,7 @@ object Test extends App { def f[b](x: ![b]): Int = x match { case send: ![c] => send.chan match { - case IC => send.data + case IC => send.data // Here, from the fact that `chan` is an IC, we need to conclude that `c` is Int. } } } diff --git a/tests/pending/pos/compound.scala b/tests/pending/pos/compound.scala index 60890f9102b9..308ffdfd981b 100644 --- a/tests/pending/pos/compound.scala +++ b/tests/pending/pos/compound.scala @@ -7,3 +7,7 @@ abstract class Test { var xx: A with B { type T; val xz: T } = null; xx = yy; } +abstract class Test2 { + var yy: A with B { type T; val xz: T } = null; + val xx: A with B { type T; val xz: T } = yy +} diff --git a/tests/pending/pos/subtypcycle.scala b/tests/pending/pos/subtypcycle.scala new file mode 100644 index 000000000000..76eb7ffec4f8 --- /dev/null +++ b/tests/pending/pos/subtypcycle.scala @@ -0,0 +1,10 @@ +object subtypcycle { + trait Y { + type A <: { type T >: B } + type B >: { type T >: A } + } + + val y: Y = ??? + val a: y.A = ??? + val b: y.B = a +} diff --git a/tests/pending/pos/t2624.scala b/tests/pending/pos/t2624.scala deleted file mode 100644 index 76f0e303698e..000000000000 --- a/tests/pending/pos/t2624.scala +++ /dev/null @@ -1,4 +0,0 @@ -object Test { - List(1).map(identity(_)) - List(1).map(identity) // this didn't typecheck before the fix -} diff --git a/tests/pending/pos/t267.scala b/tests/pending/pos/t267.scala deleted file mode 100644 index 7e5876eae99b..000000000000 --- a/tests/pending/pos/t267.scala +++ /dev/null @@ -1,55 +0,0 @@ -package expAbstractData - -/** A base class consisting of - * - a root trait (i.e. abstract class) `Exp' with an `eval' function - * - an abstract type `exp' bounded by `Exp' - * - a concrete instance class `Num' of `Exp' for numeric literals - */ -trait Base { - type exp <: Exp - - trait Exp { - def eval: Int - } - class Num(v: Int) extends Exp { self: exp => - val value = v - def eval = value - } -} - -object testBase extends App with Base { - type exp = Exp - val term = new Num(2); - Console.println(term.eval) -} - -/** Data extension: An extension of `Base' with `Plus' expressions - */ -trait BasePlus extends Base { - class Plus(l: exp, r: exp) extends Exp { self: exp => - val left = l - val right = r - def eval = left.eval + right.eval - } -} - -/** Operation extension: An extension of `Base' with 'show' methods. - */ -trait Show extends Base { - type exp <: Exp1 - - trait Exp1 extends Exp { - def show: String - } - class Num1(v: Int) extends Num(v) with Exp1 { self: exp with Num1 => - def show = value.toString() - } -} - -/** Operation extension: An extension of `BasePlus' with 'show' methods. - */ -trait ShowPlus extends BasePlus with Show { - class Plus1(l: exp, r: exp) extends Plus(l, r) with Exp1 { self: exp with Plus1 => - def show = left.show + " + " + right.show - } -} diff --git a/tests/pos/Patterns.scala b/tests/pos/Patterns.scala index 54c4d8ab27b9..e443c2ab5c3f 100644 --- a/tests/pos/Patterns.scala +++ b/tests/pos/Patterns.scala @@ -6,7 +6,7 @@ object Patterns { private def rebase(tp: NamedType): Type = { def rebaseFrom(prefix: Type): Type = ??? tp.prefix match { - case RefinedThis(rt) => rebaseFrom(rt) + case SkolemType(rt) => rebaseFrom(rt) case pre: ThisType => rebaseFrom(pre) case _ => tp } diff --git a/tests/pending/pos/bounds.scala b/tests/pos/bounds.scala similarity index 100% rename from tests/pending/pos/bounds.scala rename to tests/pos/bounds.scala diff --git a/tests/pending/pos/caseClassInMethod.scala b/tests/pos/caseClassInMethod.scala similarity index 62% rename from tests/pending/pos/caseClassInMethod.scala rename to tests/pos/caseClassInMethod.scala index 958e5dd473d2..2e6484792a08 100644 --- a/tests/pending/pos/caseClassInMethod.scala +++ b/tests/pos/caseClassInMethod.scala @@ -1,5 +1,5 @@ object t { def f = { object C; case class C(); 1 } - // pending: def g = { case class D(x: Int); object D; 2 } + def g = { case class D(x: Int); object D; 2 } def h = { case class E(y: Int = 10); 3 } } diff --git a/tests/pending/pos/class-dependent-extension-method.scala b/tests/pos/class-dependent-extension-method.scala similarity index 100% rename from tests/pending/pos/class-dependent-extension-method.scala rename to tests/pos/class-dependent-extension-method.scala diff --git a/tests/pos/refinedSubtyping.scala b/tests/pos/refinedSubtyping.scala index a01be181d65f..e6a972e1c6a8 100644 --- a/tests/pos/refinedSubtyping.scala +++ b/tests/pos/refinedSubtyping.scala @@ -60,3 +60,13 @@ class Test3 { y = x } +class Test4 { + + abstract class A { type T; val xz: Any } + + val yy: A { val xz: T } = null; +// val xx: A { val xz: T } = null; + val zz: A { val xz: T } = yy; + +} + diff --git a/tests/pos/subtyping.scala b/tests/pos/subtyping.scala index 95e813bddece..29d830dd2c8b 100644 --- a/tests/pos/subtyping.scala +++ b/tests/pos/subtyping.scala @@ -16,4 +16,17 @@ object test { } +object test2 { + + class A + class B + + val x: A | B = ??? + val y: B | A = x + + val a: A & B = ??? + val b: B & A = a + +} + diff --git a/tests/pos/t0674.scala b/tests/pos/t0674.scala index 589eeec9f322..a734091da34e 100644 --- a/tests/pos/t0674.scala +++ b/tests/pos/t0674.scala @@ -39,10 +39,10 @@ for(a <- Some(1); l <- Some(12); m <- Some(13); n <- Some(14); - o <- Some(15) -// p <- Some(16); -// q <- Some(17) -// r <- Some(18); -// s <- Some(19) + o <- Some(15); + p <- Some(16); + q <- Some(17); + r <- Some(18); + s <- Some(19) ) yield a) } diff --git a/tests/pending/pos/t1208.scala b/tests/pos/t1208.scala similarity index 100% rename from tests/pending/pos/t1208.scala rename to tests/pos/t1208.scala diff --git a/tests/pending/pos/t3020.scala b/tests/pos/t3020.scala similarity index 100% rename from tests/pending/pos/t3020.scala rename to tests/pos/t3020.scala diff --git a/tests/pending/pos/t3037.scala b/tests/pos/t3037.scala similarity index 100% rename from tests/pending/pos/t3037.scala rename to tests/pos/t3037.scala diff --git a/tests/pending/pos/t304.scala b/tests/pos/t304.scala similarity index 100% rename from tests/pending/pos/t304.scala rename to tests/pos/t304.scala diff --git a/tests/pending/pos/t3106.scala b/tests/pos/t3106.scala similarity index 100% rename from tests/pending/pos/t3106.scala rename to tests/pos/t3106.scala diff --git a/tests/pending/pos/t3137.scala b/tests/pos/t3137.scala similarity index 100% rename from tests/pending/pos/t3137.scala rename to tests/pos/t3137.scala diff --git a/tests/pending/pos/t3152.scala b/tests/pos/t3152.scala similarity index 100% rename from tests/pending/pos/t3152.scala rename to tests/pos/t3152.scala diff --git a/tests/pending/pos/t3174.scala b/tests/pos/t3174.scala similarity index 100% rename from tests/pending/pos/t3174.scala rename to tests/pos/t3174.scala diff --git a/tests/pending/pos/t3177.scala b/tests/pos/t3177.scala similarity index 100% rename from tests/pending/pos/t3177.scala rename to tests/pos/t3177.scala diff --git a/tests/pos/t8023.scala b/tests/pos/t8023.scala new file mode 100644 index 000000000000..66d478abd51e --- /dev/null +++ b/tests/pos/t8023.scala @@ -0,0 +1,9 @@ +class C[K] +class D[K] + +object Test3 { + def foo = (null: Any) match { + case a: C[k] => new C[k]() // this one worked before as the info of `A` was complete + // () + } +} diff --git a/tests/pos/t9004.scala b/tests/pos/t9004.scala new file mode 100644 index 000000000000..d591bc852a7b --- /dev/null +++ b/tests/pos/t9004.scala @@ -0,0 +1,29 @@ +object Main { + trait AA[RR] { type R = RR; def r: R } + + def test1(a: AA[_]) = { + val f = () => a.r + // The tree a.r is given the type `a.R` which normalizes + // to B', where B' is a distinct symbol ("captured existential skolem") + // to substitute for the reference to an existential skolem of B. + // + // inference of the result type of the function computes the + // packed type of tree `a.r` to make sure that terms and types + // local to the body of the function don't leak into its result + // type. The captured existential skolem is considered to be local + // so it is abstracted to its upper bound, Any. + // + // However, the packedType transformation need not have even considered + // B', as it is clear that the type `a.R` is not local to the function + // body! + f: (() => a.R) + + // The workaround is to annotate the function type, rather than + // relying in inference. + val g: (() => a.R) = () => a.r + val g2 = () => a.r + + () + } + // typer debug trace: http://rawgit.com/retronym/d5aeaf8e0a4a2e6eef4b/raw/out.html +}