Skip to content

Fix/bounds propagation v2 #238

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Nov 26, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ object SymDenotations {
final def isSetter(implicit ctx: Context) =
(this is Accessor) &&
originalName.isSetterName &&
info.firstParamTypes.nonEmpty // to avoid being fooled by var x_= : Unit = ...
(!isCompleted || info.firstParamTypes.nonEmpty) // to avoid being fooled by var x_= : Unit = ...

/** is this the constructor of a class? */
final def isClassConstructor = name == nme.CONSTRUCTOR
Expand Down
24 changes: 19 additions & 5 deletions src/dotty/tools/dotc/core/TypeApplications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -293,8 +293,9 @@ class TypeApplications(val self: Type) extends AnyVal {
/** If this is an encoding of a (partially) applied type, return its arguments,
* otherwise return Nil.
* Existential types in arguments are returned as TypeBounds instances.
* @param interpolate See argInfo
*/
final def argInfos(implicit ctx: Context): List[Type] = {
final def argInfos(interpolate: Boolean)(implicit ctx: Context): List[Type] = {
var tparams: List[TypeSymbol] = null
def recur(tp: Type, refineCount: Int): mutable.ListBuffer[Type] = tp.stripTypeVar match {
case tp @ RefinedType(tycon, name) =>
Expand All @@ -304,7 +305,7 @@ class TypeApplications(val self: Type) extends AnyVal {
if (tparams == null) tparams = tycon.typeParams
if (buf.size < tparams.length) {
val tparam = tparams(buf.size)
if (name == tparam.name) buf += tp.refinedInfo.argInfo(tparam)
if (name == tparam.name) buf += tp.refinedInfo.argInfo(tparam, interpolate)
else null
} else null
}
Expand All @@ -316,6 +317,8 @@ class TypeApplications(val self: Type) extends AnyVal {
if (buf == null) Nil else buf.toList
}

final def argInfos(implicit ctx: Context): List[Type] = argInfos(interpolate = true)

/** Argument types where existential types in arguments are disallowed */
def argTypes(implicit ctx: Context) = argInfos mapConserve noBounds

Expand All @@ -338,16 +341,27 @@ class TypeApplications(val self: Type) extends AnyVal {

/** If this is the image of a type argument to type parameter `tparam`,
* recover the type argument, otherwise NoType.
* @param interpolate If true, replace type bounds as arguments corresponding to
* variant type parameters by their dominating element. I.e. an argument
*
* T <: U
*
* for a covariant type-parameter becomes U, and an argument
*
* T >: L
*
* for a contravariant type-parameter becomes L.
*/
final def argInfo(tparam: Symbol)(implicit ctx: Context): Type = self match {
final def argInfo(tparam: Symbol, interpolate: Boolean = true)(implicit ctx: Context): Type = self match {
case TypeBounds(lo, hi) =>
if (lo eq hi) hi
else {
else if (interpolate) {
val v = tparam.variance
if (v > 0 && (lo isRef defn.NothingClass)) hi
else if (v < 0 && (hi isRef defn.AnyClass)) lo
else self // it's wildcard type; return its bounds
else self
}
else self
case _ =>
NoType
}
Expand Down
91 changes: 49 additions & 42 deletions src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,11 @@ class TypeComparer(initctx: Context) extends DotClass {
}
}

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
Expand Down Expand Up @@ -650,52 +655,54 @@ class TypeComparer(initctx: Context) extends DotClass {
}
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 TypeBounds(lo, hi) if lo eq hi =>
!ctx.phase.erasedTypes && (tp1r select name) =:= lo
case _ => false
}
})
}
val matchesParent = {
val saved = pendingRefinedBases
try {
addPendingName(name2, tp2, tp2)
isSubType(tp1, parent2)
} finally pendingRefinedBases = saved
}
(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 =>
// optimized case; all info on tp1.name1 is in refinement tp1.refinedInfo.
isSubType(normalizedInfo(tp1), tp2.refinedInfo) && {
val saved = pendingRefinedBases
try {
addPendingName(name1, tp1, tp2)
isSubType(parent1, parent2)
}
finally pendingRefinedBases = saved
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 _ =>
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 hasMatchingMember(name: Name): Boolean = /*>|>*/ ctx.traceIndented(s"hasMatchingMember($name) ${tp1.member(name).info.show}", subtyping) /*<|<*/ {
val tp1r = rebaseQual(tp1, name)
( memberMatches(tp1r member name)
||
{ // special case for situations like:
// foo <: C { type T = foo.T }
tp2.refinedInfo match {
case TypeBounds(lo, hi) if lo eq hi =>
!ctx.phase.erasedTypes && (tp1r select name) =:= lo
case _ => false
}
}
)
}
val matchesParent = {
val saved = pendingRefinedBases
try {
addPendingName(name2, tp2, tp2)
isSubType(tp1, parent2)
}
finally pendingRefinedBases = saved
}
( matchesParent && (
name2 == nme.WILDCARD
|| hasMatchingMember(name2)
|| fourthTry(tp1, tp2)
)
|| needsEtaLift(tp1, tp2) && tp1.testLifted(tp2.typeParams, isSubType(_, tp2))
)
compareRefinedSlow
}
compareRefined
case OrType(tp21, tp22) =>
Expand Down
48 changes: 45 additions & 3 deletions src/dotty/tools/dotc/core/TypeOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import config.Printers._
import Decorators._
import StdNames._
import util.SimpleMap
import collection.mutable
import ast.tpd._

trait TypeOps { this: Context =>

final def asSeenFrom(tp: Type, pre: Type, cls: Symbol, theMap: AsSeenFromMap): Type = {

def toPrefix(pre: Type, cls: Symbol, thiscls: ClassSymbol): Type = /*>|>*/ ctx.debugTraceIndented(s"toPrefix($pre, $cls, $thiscls)") /*<|<*/ {
def toPrefix(pre: Type, cls: Symbol, thiscls: ClassSymbol): Type = /*>|>*/ ctx.conditionalTraceIndented(TypeOps.track, s"toPrefix($pre, $cls, $thiscls)") /*<|<*/ {
if ((pre eq NoType) || (pre eq NoPrefix) || (cls is PackageClass))
tp
else if (thiscls.derivesFrom(cls) && pre.baseTypeRef(thiscls).exists)
Expand All @@ -24,7 +26,7 @@ trait TypeOps { this: Context =>
toPrefix(pre.baseTypeRef(cls).normalizedPrefix, cls.owner, thiscls)
}

/*>|>*/ ctx.conditionalTraceIndented(TypeOps.track , s"asSeen ${tp.show} from (${pre.show}, ${cls.show})", show = true) /*<|<*/ { // !!! DEBUG
/*>|>*/ ctx.conditionalTraceIndented(TypeOps.track, s"asSeen ${tp.show} from (${pre.show}, ${cls.show})", show = true) /*<|<*/ { // !!! DEBUG
tp match {
case tp: NamedType =>
val sym = tp.symbol
Expand Down Expand Up @@ -56,7 +58,13 @@ trait TypeOps { this: Context =>
final def simplify(tp: Type, theMap: SimplifyMap): Type = tp match {
case tp: NamedType =>
if (tp.symbol.isStatic) tp
else tp.derivedSelect(simplify(tp.prefix, theMap))
else tp.derivedSelect(simplify(tp.prefix, theMap)) match {
case tp1: NamedType if tp1.denotationIsCurrent =>
val tp2 = tp1.reduceProjection
//if (tp2 ne tp1) println(i"simplified $tp1 -> $tp2")
tp2
case tp1 => tp1
}
case tp: PolyParam =>
typerState.constraint.typeVarOfParam(tp) orElse tp
case _: ThisType | _: BoundType | NoPrefix =>
Expand Down Expand Up @@ -301,6 +309,40 @@ 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
* - the violated bound
*/
type BoundsViolation = (Tree, String, Type)

/** The list of violations where arguments are not within bounds.
* @param args The arguments
* @param boundss The list of type bounds
* @param instantiate A function that maps a bound type and the list of argument types to a resulting type.
* Needed to handle bounds that refer to other bounds.
*/
def boundsViolations(args: List[Tree], boundss: List[TypeBounds], instantiate: (Type, List[Type]) => Type)(implicit ctx: Context): List[BoundsViolation] = {
val argTypes = args.tpes
val violations = new mutable.ListBuffer[BoundsViolation]
for ((arg, bounds) <- args zip boundss) {
def checkOverlapsBounds(lo: Type, hi: Type): Unit = {
//println(i"instantiating ${bounds.hi} with $argTypes")
//println(i" = ${instantiate(bounds.hi, argTypes)}")
val hiBound = instantiate(bounds.hi, argTypes.mapConserve(_.bounds.hi))
// Note that argTypes can contain a TypeBounds type for arguments that are
// not fully determined. In that case we need to check against the hi bound of the argument.
if (!(lo <:< hiBound)) violations += ((arg, "upper", hiBound))
if (!(bounds.lo <:< hi)) violations += ((arg, "lower", bounds.lo))
}
arg.tpe match {
case TypeBounds(lo, hi) => checkOverlapsBounds(lo, hi)
case tp => checkOverlapsBounds(tp, tp)
}
}
violations.toList
}

/** Is `feature` enabled in class `owner`?
* This is the case if one of the following two alternatives holds:
*
Expand Down
Loading