Skip to content

Changes necessary to make the collection strawman compile #2240

New issue

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

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

Already on GitHub? Sign in to your account

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion compiler/src/dotty/tools/dotc/config/Config.scala
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,18 @@ object Config {
final val traceDeepSubTypeRecursions = false

/** When explaining subtypes and this flag is set, also show the classes of the compared types. */
final val verboseExplainSubtype = true
final val verboseExplainSubtype = false

/** If this flag is set, take the fast path when comparing same-named type-aliases and types */
final val fastPathForRefinedSubtype = true

/** If this flag is set, `TypeOps.normalizeToClassRefs` will insert forwarders
* for type parameters of base classes. This is an optimization, which avoids
* long alias chains. We should not rely on the optimization, though. So changing
* the flag to false can be used for checking that everything works OK without it.
*/
final val forwardTypeParams = true

/** If this flag is set, and we compute `T1 { X = S1 }` & `T2 { X = S2 }` as a new
* upper bound of a constrained parameter, try to align the refinements by computing
* `S1 =:= S2` (which might instantiate type parameters).
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ class ScalaSettings extends Settings.SettingGroup {
val deprecation = BooleanSetting("-deprecation", "Emit warning and location for usages of deprecated APIs.")
val migration = BooleanSetting("-migration", "Emit warning and location for migration issues from Scala 2.")
val encoding = StringSetting("-encoding", "encoding", "Specify character encoding used by source files.", Properties.sourceEncoding)
val explaintypes = BooleanSetting("-explaintypes", "Explain type errors in more detail.")
val explainTypes = BooleanSetting("-explain-types", "Explain type errors in more detail.")
val explainImplicits = BooleanSetting("-explain-implicits", "Explain implicit search errors in more detail.")
val explain = BooleanSetting("-explain", "Explain errors in more detail.")
val feature = BooleanSetting("-feature", "Emit warning and location for usages of features that should be imported explicitly.")
val help = BooleanSetting("-help", "Print a synopsis of standard options")
Expand Down
85 changes: 73 additions & 12 deletions compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala
Original file line number Diff line number Diff line change
Expand Up @@ -230,12 +230,47 @@ trait ConstraintHandling {
}

/** The instance type of `param` in the current constraint (which contains `param`).
* If `fromBelow` is true, the instance type is the lub of the parameter's
* lower bounds; otherwise it is the glb of its upper bounds. However,
* a lower bound instantiation can be a singleton type only if the upper bound
* is also a singleton type.
* The instance type TI is computed depending on the given variance as follows:
*
* If `variance < 0`, `TI` is the glb of `param`'s upper bounds.
* If `variance > 0`, `TI` is the lub of `param`'s lower bounds.
* However, if `TI` would be a singleton type, and the upper bound is
* not a singleton type, `TI` is widened to a non-singleton type.
* If `variance = 0`, let `Lo1` be the lower bound of `param` and let `Lo2` be the
* |-dominator of `Lo1`. If `Lo2` is not more ugly than `Lo1`, we add the
* additional constraint that `param` should be a supertype of `Lo2`.
* We then proceed as if `variance > 0`.
*
* The reason for tweaking the `variance = 0` case is that in this case we have to make
* a guess, since no possible instance type is better than the other. We apply the heuristic
* that usually an |-type is not what is intended. E.g. given two cases of types
* `Some[Int]` and `None`, we'd naturally want `Option[Int]` as the inferred type, not
* `Some[Int] | None`. If `variance > 0` it's OK to infer the smaller `|-type` since
* we can always widen later to the intended type. But in non-variant situations, we
* have to stick to the initial guess.
*
* On the other hand, sometimes the |-dominator is _not_ what we want. For instance, taking
* run/hmap.scala as an example, we have a lower bound
*
* HEntry[$param$5, V] | HEntry[String("name"), V]
*
* Its |-dominator is
*
* HEntry[_ >: $param$11 & String("cat") <: String, V]
*
* If we apply the additional constraint we get compilation failures because while
* we can find implicits for the |-type above, we cannot find implicits for the |-dominator.
* There's no hard criterion what should be considered ugly or not. For now we
* pick the degree of undefinedness of type parameters, i.e. how many type
* parameters are instantiated with wildcard types. Maybe we have to refine this
* in the future.
*
* The whole thing is clearly not nice, and I would love to have a better criterion.
* In principle we are grappling with the fundamental shortcoming that local type inference
* sometimes has to guess what was intended. The more refined our type lattice becomes,
* the harder it is to make a choice.
*/
def instanceType(param: TypeParamRef, fromBelow: Boolean): Type = {
def instanceType(param: TypeParamRef, variance: Int): Type = {
def upperBound = constraint.fullUpperBound(param)
def isSingleton(tp: Type): Boolean = tp match {
case tp: SingletonType => true
Expand All @@ -257,22 +292,48 @@ trait ConstraintHandling {
case _ => false
}

// First, solve the constraint.
// First, consider whether we need to constrain with |-dominator
if (variance == 0) {
val lo1 = ctx.typeComparer.bounds(param).lo
val lo2 = ctx.orDominator(lo1)
if (lo1 ne lo2) {
val ugliness = new TypeAccumulator[Int] {
def apply(x: Int, tp: Type) = tp match {
case tp: TypeBounds if !tp.isAlias => apply(apply(x + 1, tp.lo), tp.hi)
case tp: AndOrType => apply(x, tp.tp1) max apply(x, tp.tp2)
case _ => foldOver(x, tp)
}
}
if (ugliness(0, lo2) <= ugliness(0, lo1)) {
constr.println(i"apply additional constr $lo2 <: $param, was $lo1")
lo2 <:< param
}
else
constr.println(i"""refrain from adding constrint.
|ugliness($lo1) = ${ugliness(0, lo1)}
|ugliness($lo2) = ${ugliness(0, lo2)}""")
}
}

// Then, solve the constraint.
val fromBelow = variance >= 0
var inst = approximation(param, fromBelow)

// Then, approximate by (1.) - (3.) and simplify as follows.
// 1. If instance is from below and is a singleton type, yet
// Then, if instance is from below and is a singleton type, yet
// upper bound is not a singleton type, widen the instance.
if (fromBelow && isSingleton(inst) && !isSingleton(upperBound))
inst = inst.widen

// Then, simplify.
inst = inst.simplified

// 2. If instance is from below and is a fully-defined union type, yet upper bound
// is not a union type, approximate the union type from above by an intersection
// of all common base types.
if (fromBelow && isOrType(inst) && isFullyDefined(inst) && !isOrType(upperBound))
// Finally, if the instance is from below and is a fully-defined union type, yet upper bound
// is not a union type, harmonize the union type.
// TODO: See whether we can merge this with the special treatment of dependent params in
// simplified.
if (fromBelow && isOrType(inst) && isFullyDefined(inst) && !isOrType(upperBound)) {
inst = ctx.harmonizeUnion(inst)
}

inst
}
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Mode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ object Mode {
val TypevarsMissContext = newMode(4, "TypevarsMissContext")
val CheckCyclic = newMode(5, "CheckCyclic")

/** We are looking at the arguments of a supercall */
val InSuperCall = newMode(6, "InSuperCall")

/** Allow GADTFlexType labelled types to have their bounds adjusted */
Expand Down Expand Up @@ -81,7 +82,7 @@ object Mode {
val ReadPositions = newMode(16, "ReadPositions")

val PatternOrType = Pattern | Type

/** We are elaborating the fully qualified name of a package clause.
* In this case, identifiers should never be imported.
*/
Expand Down
32 changes: 20 additions & 12 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1381,12 +1381,15 @@ object SymDenotations {
/** Invalidate baseTypeRefCache, baseClasses and superClassBits on new run */
private def checkBasesUpToDate()(implicit ctx: Context) =
if (baseTypeRefValid != ctx.runId) {
baseTypeRefCache = new java.util.HashMap[CachedType, Type]
invalidateBaseTypeRefCache()
myBaseClasses = null
mySuperClassBits = null
baseTypeRefValid = ctx.runId
}

def invalidateBaseTypeRefCache() =
baseTypeRefCache = new java.util.HashMap[CachedType, Type]

private def computeBases(implicit ctx: Context): (List[ClassSymbol], BitSet) = {
if (myBaseClasses eq Nil) throw CyclicReference(this)
myBaseClasses = Nil
Expand Down Expand Up @@ -1712,18 +1715,23 @@ object SymDenotations {
/*>|>*/ ctx.debugTraceIndented(s"$tp.baseTypeRef($this)") /*<|<*/ {
tp match {
case tp: CachedType =>
checkBasesUpToDate()
var basetp = baseTypeRefCache get tp
if (basetp == null) {
baseTypeRefCache.put(tp, NoPrefix)
basetp = computeBaseTypeRefOf(tp)
if (isCachable(tp)) baseTypeRefCache.put(tp, basetp)
else baseTypeRefCache.remove(tp)
} else if (basetp == NoPrefix) {
baseTypeRefCache.put(tp, null)
throw CyclicReference(this)
try {
checkBasesUpToDate()
var basetp = baseTypeRefCache get tp
if (basetp == null) {
baseTypeRefCache.put(tp, NoPrefix)
basetp = computeBaseTypeRefOf(tp)
if (isCachable(tp)) baseTypeRefCache.put(tp, basetp)
else baseTypeRefCache.remove(tp)
} else if (basetp == NoPrefix)
throw CyclicReference(this)
basetp
}
catch {
case ex: Throwable =>
baseTypeRefCache.put(tp, null)
throw ex
}
basetp
case _ =>
computeBaseTypeRefOf(tp)
}
Expand Down
16 changes: 11 additions & 5 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -574,8 +574,15 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
isNewSubType(tp1.parent, tp2)
case tp1 @ HKApply(tycon1, args1) =>
compareHkApply1(tp1, tycon1, args1, tp2)
case EtaExpansion(tycon1) =>
isSubType(tycon1, tp2)
case tp1: HKTypeLambda =>
def compareHKLambda = tp1 match {
case EtaExpansion(tycon1) => isSubType(tycon1, tp2)
case _ => tp2 match {
case tp2: HKTypeLambda => false // this case was covered in thirdTry
case _ => tp2.isHK && isSubType(tp1.resultType, tp2.appliedTo(tp1.paramRefs))
}
}
compareHKLambda
case AndType(tp11, tp12) =>
// Rewrite (T111 | T112) & T12 <: T2 to (T111 & T12) <: T2 and (T112 | T12) <: T2
// and analogously for T11 & (T121 | T122) & T12 <: T2
Expand Down Expand Up @@ -775,7 +782,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
(v < 0 || isSubType(tp1, tp2))
}
isSub(args1.head, args2.head)
} && isSubArgs(args1.tail, args2.tail, tparams)
} && isSubArgs(args1.tail, args2.tail, tparams.tail)

/** Test whether `tp1` has a base type of the form `B[T1, ..., Tn]` where
* - `B` derives from one of the class symbols of `tp2`,
Expand Down Expand Up @@ -1287,8 +1294,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
tl.integrate(tparams1, tparam1.paramInfoAsSeenFrom(tp1)).bounds &
tl.integrate(tparams2, tparam2.paramInfoAsSeenFrom(tp2)).bounds),
resultTypeExp = tl =>
original(tl.integrate(tparams1, tp1).appliedTo(tl.paramRefs),
tl.integrate(tparams2, tp2).appliedTo(tl.paramRefs)))
original(tp1.appliedTo(tl.paramRefs), tp2.appliedTo(tl.paramRefs)))
}

/** Try to distribute `&` inside type, detect and handle conflicts
Expand Down
129 changes: 61 additions & 68 deletions compiler/src/dotty/tools/dotc/core/TypeOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -352,72 +352,11 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
}
}

/** If we have member definitions
*
* type argSym v= from
* type from v= to
*
* where the variances of both alias are the same, then enter a new definition
*
* type argSym v= to
*
* unless a definition for `argSym` already exists in the current scope.
*/
def forwardRef(argSym: Symbol, from: Symbol, to: TypeBounds, cls: ClassSymbol, decls: Scope) =
argSym.info match {
case info @ TypeBounds(lo2 @ TypeRef(_: ThisType, name), hi2) =>
if (name == from.name &&
(lo2 eq hi2) &&
info.variance == to.variance &&
!decls.lookup(argSym.name).exists) {
// println(s"short-circuit ${argSym.name} was: ${argSym.info}, now: $to")
enterArgBinding(argSym, to, cls, decls)
}
case _ =>
}


/** Normalize a list of parent types of class `cls` that may contain refinements
* to a list of typerefs referring to classes, by converting all refinements to member
* definitions in scope `decls`. Can add members to `decls` as a side-effect.
*/
def normalizeToClassRefs(parents: List[Type], cls: ClassSymbol, decls: Scope): List[TypeRef] = {

/** If we just entered the type argument binding
*
* type From = To
*
* and there is a type argument binding in a parent in `prefs` of the form
*
* type X = From
*
* then also add the binding
*
* type X = To
*
* to the current scope, provided (1) variances of both aliases are the same, and
* (2) X is not yet defined in current scope. This "short-circuiting" prevents
* long chains of aliases which would have to be traversed in type comparers.
*
* Note: Test i1401.scala shows that `forwardRefs` is also necessary
* for typechecking in the case where self types refer to type parameters
* that are upper-bounded by subclass instances.
*/
def forwardRefs(from: Symbol, to: Type, prefs: List[TypeRef]) = to match {
case to @ TypeBounds(lo1, hi1) if lo1 eq hi1 =>
for (pref <- prefs) {
def forward()(implicit ctx: Context): Unit =
for (argSym <- pref.decls)
if (argSym is BaseTypeArg)
forwardRef(argSym, from, to, cls, decls)
pref.info match {
case info: TempClassInfo => info.addSuspension(implicit ctx => forward())
case _ => forward()
}
}
case _ =>
}

// println(s"normalizing $parents of $cls in ${cls.owner}") // !!! DEBUG

// A map consolidating all refinements arising from parent type parameters
Expand Down Expand Up @@ -460,16 +399,70 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
s"redefinition of ${decls.lookup(name).debugString} in ${cls.showLocated}")
enterArgBinding(formals(name), refinedInfo, cls, decls)
}
// Forward definitions in super classes that have one of the refined parameters
// as aliases directly to the refined info.
// Note that this cannot be fused with the previous loop because we now
// assume that all arguments have been entered in `decls`.
refinements foreachBinding { (name, refinedInfo) =>
forwardRefs(formals(name), refinedInfo, parentRefs)
}

if (Config.forwardTypeParams)
forwardParamBindings(parentRefs, refinements, cls, decls)

parentRefs
}

/** Forward parameter bindings in baseclasses to argument types of
* class `cls` if possible.
* If there have member definitions
*
* type param v= middle
* type middle v= to
*
* where the variances of both alias are the same, then enter a new definition
*
* type param v= to
*
* If multiple forwarders would be generated, join their `to` types with an `&`.
*
* @param cls The class for which parameter bindings should be forwarded
* @param decls Its scope
* @param parentRefs The parent type references of `cls`
* @param paramBindings The type parameter bindings generated for `cls`
*
*/
def forwardParamBindings(parentRefs: List[TypeRef],
paramBindings: SimpleMap[TypeName, Type],
cls: ClassSymbol, decls: Scope)(implicit ctx: Context) = {

def forwardRef(argSym: Symbol, from: TypeName, to: TypeAlias) = argSym.info match {
case info @ TypeAlias(TypeRef(_: ThisType, `from`)) if info.variance == to.variance =>
val existing = decls.lookup(argSym.name)
if (existing.exists) existing.info = existing.info & to
else enterArgBinding(argSym, to, cls, decls)
case _ =>
}

def forwardRefs(from: TypeName, to: Type) = to match {
case to: TypeAlias =>
for (pref <- parentRefs) {
def forward()(implicit ctx: Context): Unit =
for (argSym <- pref.decls)
if (argSym is BaseTypeArg) forwardRef(argSym, from, to)
pref.info match {
case info: TempClassInfo => info.addSuspension(implicit ctx => forward())
case _ => forward()
}
}
case _ =>
}

paramBindings.foreachBinding(forwardRefs)
}

/** Used only for debugging: All BaseTypeArg definitions in
* `cls` and all its base classes.
*/
def allBaseTypeArgs(cls: ClassSymbol)(implicit ctx: Context) =
for { bc <- cls.baseClasses
sym <- bc.info.decls.toList
if sym.is(BaseTypeArg)
} yield sym

/** An argument bounds violation is a triple consisting of
* - the argument tree
* - a string "upper" or "lower" indicating which bound is violated
Expand Down
Loading