-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add refining annotations #4626
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
Add refining annotations #4626
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -236,8 +236,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { | |
compareWild | ||
case tp2: LazyRef => | ||
!tp2.evaluating && recur(tp1, tp2.ref) | ||
case tp2: AnnotatedType => | ||
recur(tp1, tp2.tpe) // todo: refine? | ||
case tp2: AnnotatedType if !tp2.isRefining => | ||
recur(tp1, tp2.tpe) | ||
case tp2: ThisType => | ||
def compareThis = { | ||
val cls2 = tp2.cls | ||
|
@@ -345,13 +345,13 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { | |
// because that would cause an assertionError. Return false instead. | ||
// See i859.scala for an example where we hit this case. | ||
!tp1.evaluating && recur(tp1.ref, tp2) | ||
case tp1: AnnotatedType => | ||
case tp1: AnnotatedType if !tp1.isRefining => | ||
recur(tp1.tpe, tp2) | ||
case AndType(tp11, tp12) => | ||
if (tp11.stripTypeVar eq tp12.stripTypeVar) recur(tp11, tp2) | ||
else thirdTry | ||
case tp1 @ OrType(tp11, tp12) => | ||
def joinOK = tp2.dealias match { | ||
def joinOK = tp2.dealiasKeepRefiningAnnots match { | ||
case tp2: AppliedType if !tp2.tycon.typeSymbol.isClass => | ||
// If we apply the default algorithm for `A[X] | B[Y] <: C[Z]` where `C` is a | ||
// type parameter, we will instantiate `C` to `A` and then fail when comparing | ||
|
@@ -510,7 +510,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { | |
} | ||
compareTypeLambda | ||
case OrType(tp21, tp22) => | ||
val tp1a = tp1.widenDealias | ||
val tp1a = tp1.widenDealiasKeepRefiningAnnots | ||
if (tp1a ne tp1) | ||
// Follow the alias; this might avoid truncating the search space in the either below | ||
// Note that it's safe to widen here because singleton types cannot be part of `|`. | ||
|
@@ -567,6 +567,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { | |
false | ||
} | ||
compareTypeBounds | ||
case tp2: AnnotatedType if tp2.isRefining => | ||
(tp1.derivesAnnotWith(tp2.annot.sameAnnotation) || defn.isBottomType(tp1)) && | ||
recur(tp1, tp2.tpe) | ||
case ClassInfo(pre2, cls2, _, _, _) => | ||
def compareClassInfo = tp1 match { | ||
case ClassInfo(pre1, cls1, _, _, _) => | ||
|
@@ -641,7 +644,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { | |
} | ||
compareHKLambda | ||
case AndType(tp11, tp12) => | ||
val tp2a = tp2.dealias | ||
val tp2a = tp2.dealiasKeepRefiningAnnots | ||
if (tp2a ne tp2) // Follow the alias; this might avoid truncating the search space in the either below | ||
return recur(tp1, tp2a) | ||
|
||
|
@@ -661,6 +664,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { | |
case _ => | ||
} | ||
either(recur(tp11, tp2), recur(tp12, tp2)) | ||
case tp1: AnnotatedType if tp1.isRefining => | ||
isNewSubType(tp1.tpe) | ||
case JavaArrayType(elem1) => | ||
def compareJavaArray = tp2 match { | ||
case JavaArrayType(elem2) => isSubType(elem1, elem2) | ||
|
@@ -685,13 +690,13 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { | |
*/ | ||
def isMatchingApply(tp1: Type): Boolean = tp1 match { | ||
case AppliedType(tycon1, args1) => | ||
tycon1.dealias match { | ||
tycon1.dealiasKeepRefiningAnnots match { | ||
case tycon1: TypeParamRef => | ||
(tycon1 == tycon2 || | ||
canConstrain(tycon1) && tryInstantiate(tycon1, tycon2)) && | ||
isSubArgs(args1, args2, tp1, tparams) | ||
case tycon1: TypeRef => | ||
tycon2.dealias match { | ||
tycon2.dealiasKeepRefiningAnnots match { | ||
case tycon2: TypeRef if tycon1.symbol == tycon2.symbol => | ||
isSubType(tycon1.prefix, tycon2.prefix) && | ||
isSubArgs(args1, args2, tp1, tparams) | ||
|
@@ -700,7 +705,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { | |
} | ||
case tycon1: TypeVar => | ||
isMatchingApply(tycon1.underlying) | ||
case tycon1: AnnotatedType => | ||
case tycon1: AnnotatedType if !tycon1.isRefining => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure when a type constructor would have a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, well spotted. Narrowing the lower bound is always safe, and it is the only thing we can do in this case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like this PR was merged without addressing this comment? |
||
isMatchingApply(tycon1.underlying) | ||
case _ => | ||
false | ||
|
@@ -811,7 +816,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { | |
fourthTry | ||
} | ||
} | ||
case _: TypeVar | _: AnnotatedType => | ||
case _: TypeVar => | ||
recur(tp1, tp2.superType) | ||
case tycon2: AnnotatedType if !tycon2.isRefining => | ||
recur(tp1, tp2.superType) | ||
case tycon2: AppliedType => | ||
fallback(tycon2.lowerBound) | ||
|
@@ -1097,11 +1104,10 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { | |
* is some combination of TypeRefs that point to classes, where the | ||
* combiners are AppliedTypes, RefinedTypes, RecTypes, And/Or-Types or AnnotatedTypes. | ||
*/ | ||
private def isCovered(tp: Type): Boolean = tp.dealias.stripTypeVar match { | ||
private def isCovered(tp: Type): Boolean = tp.dealiasKeepRefiningAnnots.stripTypeVar match { | ||
case tp: TypeRef => tp.symbol.isClass && tp.symbol != NothingClass && tp.symbol != NullClass | ||
case tp: AppliedType => isCovered(tp.tycon) | ||
case tp: RefinedOrRecType => isCovered(tp.parent) | ||
case tp: AnnotatedType => isCovered(tp.underlying) | ||
case tp: AndType => isCovered(tp.tp1) && isCovered(tp.tp2) | ||
case tp: OrType => isCovered(tp.tp1) && isCovered(tp.tp2) | ||
case _ => false | ||
|
@@ -1546,7 +1552,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { | |
} | ||
case tp1: TypeVar if tp1.isInstantiated => | ||
tp1.underlying & tp2 | ||
case tp1: AnnotatedType => | ||
case tp1: AnnotatedType if !tp1.isRefining => | ||
tp1.underlying & tp2 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not rewrap the annotation around the intersection if it's a refining annotation? |
||
case _ => | ||
NoType | ||
|
@@ -1565,7 +1571,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { | |
ExprType(rt1 | tp2.widenExpr) | ||
case tp1: TypeVar if tp1.isInstantiated => | ||
tp1.underlying | tp2 | ||
case tp1: AnnotatedType => | ||
case tp1: AnnotatedType if !tp1.isRefining => | ||
tp1.underlying | tp2 | ||
case _ => | ||
NoType | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -283,6 +283,15 @@ object Types { | |
case _ => false | ||
} | ||
|
||
/** Does this type have a supertype with an annotation satisfying given predicate `p`? */ | ||
def derivesAnnotWith(p: Annotation => Boolean)(implicit ctx: Context): Boolean = this match { | ||
case tp: AnnotatedType => p(tp.annot) || tp.tpe.derivesAnnotWith(p) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider renaming There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good idea. |
||
case tp: TypeProxy => tp.superType.derivesAnnotWith(p) | ||
case AndType(l, r) => l.derivesAnnotWith(p) || r.derivesAnnotWith(p) | ||
case OrType(l, r) => l.derivesAnnotWith(p) && r.derivesAnnotWith(p) | ||
case _ => false | ||
} | ||
|
||
/** Does this type occur as a part of type `that`? */ | ||
final def occursIn(that: Type)(implicit ctx: Context): Boolean = | ||
that existsPart (this == _) | ||
|
@@ -922,6 +931,11 @@ object Types { | |
*/ | ||
def stripAnnots(implicit ctx: Context): Type = this | ||
|
||
def rewrapAnnots(tp: Type)(implicit ctx: Context): Type = tp.stripTypeVar match { | ||
case AnnotatedType(tp1, annot) => AnnotatedType(rewrapAnnots(tp1), annot) | ||
case _ => this | ||
} | ||
|
||
/** Strip PolyType prefix */ | ||
def stripPoly(implicit ctx: Context): Type = this match { | ||
case tp: PolyType => tp.resType.stripPoly | ||
|
@@ -1008,48 +1022,57 @@ object Types { | |
this | ||
} | ||
|
||
private def dealias1(keepAnnots: Boolean)(implicit ctx: Context): Type = this match { | ||
private def dealias1(keep: AnnotatedType => Context => Boolean)(implicit ctx: Context): Type = this match { | ||
case tp: TypeRef => | ||
if (tp.symbol.isClass) tp | ||
else tp.info match { | ||
case TypeAlias(alias) => alias.dealias1(keepAnnots): @tailrec | ||
case TypeAlias(alias) => alias.dealias1(keep): @tailrec | ||
case _ => tp | ||
} | ||
case app @ AppliedType(tycon, args) => | ||
val tycon1 = tycon.dealias1(keepAnnots) | ||
if (tycon1 ne tycon) app.superType.dealias1(keepAnnots): @tailrec | ||
val tycon1 = tycon.dealias1(keep) | ||
if (tycon1 ne tycon) app.superType.dealias1(keep): @tailrec | ||
else this | ||
case tp: TypeVar => | ||
val tp1 = tp.instanceOpt | ||
if (tp1.exists) tp1.dealias1(keepAnnots): @tailrec else tp | ||
if (tp1.exists) tp1.dealias1(keep): @tailrec else tp | ||
case tp: AnnotatedType => | ||
val tp1 = tp.tpe.dealias1(keepAnnots) | ||
if (keepAnnots) tp.derivedAnnotatedType(tp1, tp.annot) else tp1 | ||
val tp1 = tp.tpe.dealias1(keep) | ||
if (keep(tp)(ctx)) tp.derivedAnnotatedType(tp1, tp.annot) else tp1 | ||
case tp: LazyRef => | ||
tp.ref.dealias1(keepAnnots): @tailrec | ||
tp.ref.dealias1(keep): @tailrec | ||
case _ => this | ||
} | ||
|
||
/** Follow aliases and dereferences LazyRefs, annotated types and instantiated | ||
* TypeVars until type is no longer alias type, annotated type, LazyRef, | ||
* or instantiated type variable. | ||
*/ | ||
final def dealias(implicit ctx: Context): Type = dealias1(keepNever) | ||
|
||
/** Follow aliases and dereferences LazyRefs and instantiated TypeVars until type | ||
* is no longer alias type, LazyRef, or instantiated type variable. | ||
* Goes through annotated types and rewraps annotations on the result. | ||
*/ | ||
final def dealiasKeepAnnots(implicit ctx: Context): Type = | ||
dealias1(keepAnnots = true) | ||
final def dealiasKeepAnnots(implicit ctx: Context): Type = dealias1(keepAlways) | ||
|
||
/** Follow aliases and dereferences LazyRefs, annotated types and instantiated | ||
* TypeVars until type is no longer alias type, annotated type, LazyRef, | ||
* or instantiated type variable. | ||
*/ | ||
final def dealias(implicit ctx: Context): Type = | ||
dealias1(keepAnnots = false) | ||
/** Like `dealiasKeepAnnots`, but keeps only refining annotations */ | ||
final def dealiasKeepRefiningAnnots(implicit ctx: Context): Type = dealias1(keepIfRefining) | ||
|
||
/** Perform successive widenings and dealiasings until none can be applied anymore */ | ||
@tailrec final def widenDealias(implicit ctx: Context): Type = { | ||
val res = this.widen.dealias | ||
if (res eq this) res else res.widenDealias | ||
private def widenDealias1(keep: AnnotatedType => Context => Boolean)(implicit ctx: Context): Type = { | ||
val res = this.widen.dealias1(keep) | ||
if (res eq this) res else res.widenDealias1(keep) | ||
} | ||
|
||
/** Perform successive widenings and dealiasings until none can be applied anymore */ | ||
final def widenDealias(implicit ctx: Context): Type = widenDealias1(keepNever) | ||
|
||
/** Perform successive widenings and dealiasings while rewrapping annotations, until none can be applied anymore */ | ||
final def widenDealiasKeepAnnots(implicit ctx: Context): Type = widenDealias1(keepAlways) | ||
|
||
/** Perform successive widenings and dealiasings while rewrapping refining annotations, until none can be applied anymore */ | ||
final def widenDealiasKeepRefiningAnnots(implicit ctx: Context): Type = widenDealias1(keepIfRefining) | ||
|
||
/** Widen from constant type to its underlying non-constant | ||
* base type. | ||
*/ | ||
|
@@ -1796,8 +1819,8 @@ object Types { | |
case arg: TypeBounds => | ||
val v = param.paramVariance | ||
val pbounds = param.paramInfo | ||
if (v > 0 && pbounds.loBound.dealias.isBottomType) TypeAlias(arg.hiBound & rebase(pbounds.hiBound)) | ||
else if (v < 0 && pbounds.hiBound.dealias.isTopType) TypeAlias(arg.loBound | rebase(pbounds.loBound)) | ||
if (v > 0 && pbounds.loBound.dealiasKeepAnnots.isBottomType) TypeAlias(arg.hiBound & rebase(pbounds.hiBound)) | ||
else if (v < 0 && pbounds.hiBound.dealiasKeepAnnots.isTopType) TypeAlias(arg.loBound | rebase(pbounds.loBound)) | ||
else arg recoverable_& rebase(pbounds) | ||
case arg => TypeAlias(arg) | ||
} | ||
|
@@ -3693,6 +3716,17 @@ object Types { | |
|
||
override def stripAnnots(implicit ctx: Context): Type = tpe.stripAnnots | ||
|
||
private[this] var isRefiningKnown = false | ||
private[this] var isRefiningCache: Boolean = _ | ||
|
||
def isRefining(implicit ctx: Context) = { | ||
if (!isRefiningKnown) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't the cache be invalidated at the start of a new run? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was thinking about that too, but decided that it's probably fine given the internal nature of such annotations. Then again, we do expose RefiningAnnotation in the public |
||
isRefiningCache = annot.symbol.derivesFrom(defn.RefiningAnnotationClass) | ||
isRefiningKnown = true | ||
} | ||
isRefiningCache | ||
} | ||
|
||
override def iso(that: Any, bs: BinderPairs): Boolean = that match { | ||
case that: AnnotatedType => tpe.equals(that.tpe, bs) && (annot `eq` that.annot) | ||
case _ => false | ||
|
@@ -4145,11 +4179,12 @@ object Types { | |
*/ | ||
def tryWiden(tp: NamedType, pre: Type): Type = pre.member(tp.name) match { | ||
case d: SingleDenotation => | ||
d.info.dealias match { | ||
val tp1 = d.info.dealiasKeepAnnots | ||
tp1.stripAnnots match { | ||
case TypeAlias(alias) => | ||
// if H#T = U, then for any x in L..H, x.T =:= U, | ||
// hence we can replace with U under all variances | ||
reapply(alias) | ||
reapply(alias.rewrapAnnots(tp1)) | ||
case TypeBounds(lo, hi) => | ||
// If H#T = _ >: S <: U, then for any x in L..H, S <: x.T <: U, | ||
// hence we can replace with S..U under all variances | ||
|
@@ -4575,7 +4610,7 @@ object Types { | |
case _ => false | ||
} | ||
|
||
// ----- Decorator implicits -------------------------------------------- | ||
// ----- Helpers and Decorator implicits -------------------------------------- | ||
|
||
implicit def decorateTypeApplications(tpe: Type): TypeApplications = new TypeApplications(tpe) | ||
|
||
|
@@ -4588,4 +4623,8 @@ object Types { | |
else tps2.nonEmpty && tps1.head.equals(tps2.head, bs) && tps1.tail.equalElements(tps2.tail, bs) | ||
} | ||
} | ||
|
||
private val keepAlways: AnnotatedType => Context => Boolean = _ => _ => true | ||
private val keepNever: AnnotatedType => Context => Boolean = _ => _ => false | ||
private val keepIfRefining: AnnotatedType => Context => Boolean = tp => ctx => tp.isRefining(ctx) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Misaligned
=
)