Skip to content

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

Merged
merged 5 commits into from
Jun 8, 2018
Merged
Show file tree
Hide file tree
Changes from 3 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 compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ object desugar {
case tp: NamedType if tp.symbol.exists && (tp.symbol.owner eq originalOwner) =>
val defctx = ctx.outersIterator.dropWhile(_.scope eq ctx.scope).next()
var local = defctx.denotNamed(tp.name ++ suffix).suchThat(_.isParamOrAccessor).symbol
if (local.exists) (defctx.owner.thisType select local).dealias
if (local.exists) (defctx.owner.thisType select local).dealiasKeepAnnots
else {
def msg =
s"no matching symbol for ${tp.symbol.showLocated} in ${defctx.owner} / ${defctx.effectiveScope.toList}"
Expand Down
25 changes: 25 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,31 @@ object Trees {

override def toText(printer: Printer) = printer.toText(this)

def sameTree(that: Tree[_]): Boolean = {
def isSame(x: Any, y: Any): Boolean =
x.asInstanceOf[AnyRef].eq(y.asInstanceOf[AnyRef]) || {
x match {
case x: Tree[_] =>
y match {
case y: Tree[_] => x.sameTree(y)
case _ => false
}
case x: List[_] =>
y match {
case y: List[_] => x.corresponds(y)(isSame)
case _ => false
}
case _ =>
false
}
}
this.getClass == that.getClass && {
val it1 = this.productIterator
val it2 = that.productIterator
it1.corresponds(it2)(isSame)
}
}

override def hashCode(): Int = uniqueId // for debugging; was: System.identityHashCode(this)
override def equals(that: Any) = this eq that.asInstanceOf[AnyRef]

Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -963,7 +963,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
val alternatives = ctx.typer.resolveOverloaded(allAlts, proto)
assert(alternatives.size == 1,
i"${if (alternatives.isEmpty) "no" else "multiple"} overloads available for " +
i"$method on ${receiver.tpe.widenDealias} with targs: $targs%, %; args: $args%, % of types ${args.tpes}%, %; expectedType: $expectedType." +
i"$method on ${receiver.tpe.widenDealiasKeepAnnots} with targs: $targs%, %; args: $args%, % of types ${args.tpes}%, %; expectedType: $expectedType." +
i" isAnnotConstructor = $isAnnotConstructor.\n" +
i"all alternatives: ${allAlts.map(_.symbol.showDcl).mkString(", ")}\n" +
i"matching alternatives: ${alternatives.map(_.symbol.showDcl).mkString(", ")}.") // this is parsed from bytecode tree. there's nothing user can do about it
Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Annotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ object Annotations {
def isEvaluated: Boolean = true

def ensureCompleted(implicit ctx: Context): Unit = tree

def sameAnnotation(that: Annotation)(implicit ctx: Context) =
symbol == that.symbol && tree.sameTree(that.tree)
}

case class ConcreteAnnotation(t: Tree) extends Annotation {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ trait ConstraintHandling {
case tp: OrType => isFullyDefined(tp.tp1) && isFullyDefined(tp.tp2)
case _ => true
}
def isOrType(tp: Type): Boolean = tp.stripTypeVar.dealias match {
def isOrType(tp: Type): Boolean = tp.dealias match {
case tp: OrType => true
case tp: RefinedOrRecType => isOrType(tp.parent)
case AndType(tp1, tp2) => isOrType(tp1) | isOrType(tp2)
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,8 @@ class Definitions {
def ClassfileAnnotationClass(implicit ctx: Context) = ClassfileAnnotationType.symbol.asClass
lazy val StaticAnnotationType = ctx.requiredClassRef("scala.annotation.StaticAnnotation")
def StaticAnnotationClass(implicit ctx: Context) = StaticAnnotationType.symbol.asClass
lazy val RefiningAnnotationType = ctx.requiredClassRef("scala.annotation.RefiningAnnotation")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Misaligned =)

def RefiningAnnotationClass(implicit ctx: Context) = RefiningAnnotationType.symbol.asClass

// Annotation classes
lazy val AliasAnnotType = ctx.requiredClassRef("scala.annotation.internal.Alias")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
* that are not top-level are not affected.
*/
def replace(param: TypeParamRef, tp: Type)(implicit ctx: Context): OrderingConstraint = {
val replacement = tp.dealias.stripTypeVar
val replacement = tp.dealiasKeepAnnots.stripTypeVar
if (param == replacement) this
else {
assert(replacement.isValueTypeOrLambda)
Expand Down
34 changes: 20 additions & 14 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 `|`.
Expand Down Expand Up @@ -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, _, _, _) =>
Expand Down Expand Up @@ -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)

Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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 =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure when a type constructor would have a RefiningAnnotation, but wouldn't it in principle be okay to widen here, regardless of whether the annotation is refining?

Copy link
Contributor Author

@odersky odersky Jun 8, 2018

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The 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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The 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
Expand All @@ -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
Expand Down
89 changes: 64 additions & 25 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider renaming AnnotatedType#tpe to #parent or #info? (I find tp.tpe slightly confusing.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 == _)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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) {
Copy link
Member

Choose a reason for hiding this comment

The 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?

Copy link
Contributor

Choose a reason for hiding this comment

The 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 scala.annotation package.

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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -4575,7 +4610,7 @@ object Types {
case _ => false
}

// ----- Decorator implicits --------------------------------------------
// ----- Helpers and Decorator implicits --------------------------------------

implicit def decorateTypeApplications(tpe: Type): TypeApplications = new TypeApplications(tpe)

Expand All @@ -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)
}
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ object Applications {
val ref = extractorMember(tp, name)
if (ref.isOverloaded)
errorType(i"Overloaded reference to $ref is not allowed in extractor", errorPos)
ref.info.widenExpr.annotatedToRepeated.dealias
ref.info.widenExpr.annotatedToRepeated.dealiasKeepAnnots
}

/** Does `tp` fit the "product match" conditions as an unapply result type
Expand Down
Loading