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

Conversation

odersky
Copy link
Contributor

@odersky odersky commented Jun 7, 2018

Adds a new kind of annotation that conceptually refines the annotated type. That is, refining annotations yield subtypes. They are more "sticky" than normal annotations. In a number of situations they are not stripped away where normal annotations are.

It is intended that this kind of annotation is a good basis to define TypeOf types.

odersky added 2 commits June 7, 2018 11:13
The idea is that such annotations create some proper subtype of the type
they annotate. Hence, they cannot be stripped away like normal annotations are.
 - some keep their annotations
 - some only keep refining annotations
 - most of them still strip all annotations
@odersky odersky mentioned this pull request Jun 7, 2018
@odersky
Copy link
Contributor Author

odersky commented Jun 7, 2018

@gsps I recommend to use this status as a basis for further work.

Next step could be to define an annotation like TypeOf in

library/src/scala/annotation/internal

Copy link
Contributor

@OlivierBlanvillain OlivierBlanvillain left a comment

Choose a reason for hiding this comment

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

Should ConstantType and TermRef be refactored into AnnotatedType with RefiningAnnotation?

* Refining annotations are more "sticky" than normal ones. They are conceptually kept
* around when normal refinements would also not be stripped away.
*/
trait RefiningAnnotation extends StaticAnnotation
Copy link
Contributor

Choose a reason for hiding this comment

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

Why expose that in the stdlib, isn't this compiler internal only?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, you might want to define your own annotations (say units of measure). Maybe they need a compiler plugin to order to be checked, or maybe you can do it in the future with annotation macros. RefiningAnnotation just makes sure they propagate correctly.

@odersky
Copy link
Contributor Author

odersky commented Jun 7, 2018

Should ConstantType and TermRef be refactored into AnnotatedType with RefiningAnnotation?

In principle, we could consider this. But these two are a little bit different since they are also the main carriers of singleton types. Also the terms they capture are not very general, so it's not clear what we would win.

Copy link
Contributor

@gsps gsps left a comment

Choose a reason for hiding this comment

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

This looks nice! At this point, I'd actually be quite tempted to also add DistributiveRefiningAnnotation along with the appropriate cases to distributiveOr and distributiveAnd ;-)

In any case, I'll give it a shot and try to rebase our WIP transparent PR on this.

@@ -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 =)

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

@@ -8,6 +8,7 @@ import core.Types.WildcardType
object common {

val alwaysTrue = Function.const(true) _
val alwaysFalse = Function.const(false) _
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this used anywhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No. I used it in an intermediate version. I kept it for duality.

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

@odersky odersky force-pushed the add-refining-annotations branch from a4745fd to 81a5cf9 Compare June 8, 2018 08:00
@gsps
Copy link
Contributor

gsps commented Jun 8, 2018

LGTM.

@odersky odersky merged commit 25ee50b into scala:master Jun 8, 2018
@@ -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?


override def stripAnnots(implicit ctx: Context): Type = tpe.stripAnnots
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.

@smarter
Copy link
Member

smarter commented Jun 8, 2018

This PR would really really benefits from having tests.

@allanrenucci allanrenucci deleted the add-refining-annotations branch June 8, 2018 18:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants